条件付き演算子を使用するときに、Cが文字列の連結を許可しないのはなぜですか?


95

次のコードは問題なくコンパイルされます。

int main() {
    printf("Hi" "Bye");
}

ただし、これはコンパイルされません。

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}

その理由は何ですか?


95
文字列の連結は、初期の字句解析フェーズの一部です。これは、Cの式synatxの一部ではありません。つまり、「文字列リテラル」型のはありません。文字列リテラルは、値を形成するソースコードの字句要素です。
Kerrek SB 2016

24
@KerrekSBの回答を明確にするために-文字列の連結は、コンパイルのコードテキストの処理の一部です。三次演算子はランタイムで評価されますが、コードがコンパイルされた後(またはすべてが定数である場合は、コンパイル時に実行できます)。
Eugene Sh。

2
詳細:この記事では、"Hi""Bye"されている文字列リテラルではなく、文字列 C標準ライブラリで使用されるように。では、文字列リテラル、コンパイラが連結します"H\0i" "B\0ye"。同じではありませんsprintf(buf,"%s%s", "H\0i" "B\0ye");
chux-モニカを復活させる

15
多かれ少なかれ同じことはできませんa (some_condition ? + : - ) b
user253751

4
printf("Hi" ("Bye"));動作しないことにも注意してください。三項演算子は必要ありません。括弧で十分です(ただし、printf("Hi" test ? "Bye" : "Goodbye")コンパイルされません)。文字列リテラルに続くことができるトークンの数は限られています。カンマ,、角かっこを開く、角かっこ[を閉じる](例のように1["abc"]—はい、それは厄介です)、丸かっこ)を閉じる(中かっこを閉じる}(初期化子または同様のコンテキスト))、セミコロン;は正当です(および別の文字列リテラル); 他にあるかどうかはわかりません。
ジョナサンレフラー

回答:


121

C標準に準拠(5.1.1.2翻訳段階)

1変換の構文規則間の優先順位は、次のフェーズで指定されます。6)

  1. 隣接する文字列リテラルトークンが連結されます。

そしてその後だけ

  1. トークンを区切る空白文字は重要ではなくなりました。各前処理トークンはトークンに変換されます。結果として得られるトークンは、構文的および意味的に分析され、翻訳単位として翻訳されます。

この構造では

"Hi" (test ? "Bye" : "Goodbye")

隣接する文字列リテラルトークンはありません。したがって、この構成は無効です。


43
これは、Cで許可されていないという主張を繰り返すだけです。問題だった理由が説明されていません。5時間で26の賛成票を獲得した理由がわかりません。おめでとう。
オービットのライトネスレース2016年

4
ここの@LightnessRacesinOrbitに同意する必要があります。なぜべきではありません(test ? "Bye" : "Goodbye")evaulate文字列リテラルのいずれかに実質的に作ります "Hi" "Bye""Hi Goodbye"?(私の質問は他の回答で回答されています)
非常識

48
@LightnessRacesinOrbit、なぜなら、人々が何かがCでコンパイルできない理由を通常尋ねるとき、彼らはそれが破られるルールについての明確化を求めているのであり、なぜ古代の標準作者がそれをそのように選択したのかではないからです。
user1717828 '17年

4
@LightnessRacesinOrbitあなたが説明する質問はおそらくトピックから外れているでしょう。それを実装することができないという技術的な理由はわかりません。そのため、仕様の作成者からの明確な回答がないと、すべての回答は意見に基づいたものになります。また、一般的には「実用的な」質問や「回答可能な」質問のカテゴリには分類されません(ヘルプセンターで必要とされるため)。
jpmc26 2016年

12
@LightnessRacesinOrbitそれは理由を説明します:「C標準がそう言ったから」。このルールが定義どおりに定義されている理由についての質問はトピックから外れます。
user11153

135

C11標準の§5.1.1.2章に従って、隣接する文字列リテラルの連結:

隣接する文字列リテラルトークンが連結されます。

翻訳段階で発生します。一方:

printf("Hi" (test ? "Bye" : "Goodbye"));

実行時に評価される条件演算子が含まれます。そのため、コンパイル時には、翻訳フェーズ中に、隣接する文字列リテラルが存在しないため、連結できません。構文が無効であるため、コンパイラによって報告されました。


why部分について少し詳しく説明すると、前処理フェーズ中に、隣接する文字列リテラルが連結され、単一の文字列リテラル(トークン)として表されます。ストレージはそれに応じて割り当てられ、連結された文字列リテラルは単一のエンティティと見なされます(1つの文字列リテラル)ます。

一方、実行時の連結の場合、宛先には連結された文字列リテラルを保持するのに十分なメモリが必要です。そうでない場合、予期された連結れた出力にアクセスできません。現在、文字列リテラルの場合、それらはコンパイル時にすでにメモリに割り当てられており、元のコンテンツに入力たり、元のコンテンツに追加たりするために拡張することはできません。つまり、連結された結果に単一の文字列リテラルとしてアクセス(表示)する方法はありません。したがって、この構成は本質的に正しくありません。

参考までに、ランタイム文字列リテラルではない)連結の場合、strcat()2つの文字列を連結するライブラリ関数があります。注意、説明には次のものが記載されています:

char *strcat(char * restrict s1,const char * restrict s2);

strcat()関数は、文字列のコピーを追加することで指さs2の最後に(終端のヌル文字を含む)によって指された文字列s1。の最初の文字はs2、の末尾のヌル文字を上書きしますs1。[...]

だから、私たちが見ることができ、s1ある文字列ではなく、文字列リテラル。ただし、の内容s2はまったく変更されないため、文字列リテラルにすることができます


についての追加説明がstrcat必要な場合があります。宛先配列はs2、そこに存在する文字の後にnullターミネータとnullターミネータから文字を受け取るのに十分な長さでなければなりません。
-chqrlie

39

文字列リテラル連結は、コンパイル時にプリプロセッサによって実行されます。この連結がの値を認識する方法はありません。この値はtest、プログラムが実際に実行されるまでわかりません。したがって、これらの文字列リテラルは連結できません。

一般的なケースでは、コンパイル時に既知の値に対してこのような構造を持たないため、C規格は自動連結機能を最も基本的なケースに制限するように設計されました。 。

しかし、この制限がそのように表現されていない場合や、制限の構成が異なっている場合でも、連結をランタイムプロセスにしないと、この例を実現することはできません。そして、そのために、などのライブラリ関数がありますstrcat


3
仮定を読んだだけです。あなたが言うことはかなり有効ですが、何もないのでそのソースを提供することはできません。Cに関する唯一の出典は、(多くの場合は不注意であるが)なぜあるものがそうであるのかを述べておらず、特定の方法でなければならないことを述べているだけの標準文書です。したがって、モスクワの答えからのヴラドについてのほのぼのした態度を守ることは不適切です。OPは「なぜそうなのか」に分解できるので -出典が正しい唯一の回答が「Cであるため、Cが定義されている方法である」それが唯一の文字通りの正解です。
dhein

1
これは(認められた)説明不足です。しかし、ここで再び言われているのは、ヴラドの答えがあなたの核心の問題の説明としてはるかに役立つということです。もう一度言います。私が確認できる情報は関連していて正しいものですが、申し立てには同意しません。そして、私はあなたのトピックがオフトピックであることも考慮しませんが、それは私のPOVからはVladsよりもオフトピックです。
dhein

11
@ザイビス:ソースは私です。ヴラドの答えは説明ではありません。質問の前提の確認にすぎません。確かにどちらも「オフトピック」ではありません(その用語の意味を調べたい場合があります)。しかし、あなたにはあなたの意見が与えられます。
オービットのライトネスレース2016年

上記のコメントを読んだ後でも、誰がこの回答に反対票を投じたのか疑問に思いますOP OP OPがこの回答についてさらに説明を求めない限り、これは完璧な回答だと思います。
Mohit Jain

2
私がこの答えがあなたと@ VladfromMoscow'sに受け入れられる理由を区別することはできません。両者が同じことを言うとき、そして彼が引用に裏付けられており、あなたの答えはそうではないときです。
ローン侯爵

30

Cにはstring型がないからです。文字列リテラルはcharchar*ポインターによって参照される配列にコンパイルされます。

Cでは、最初の例のように、隣接するリテラルコンパイル時に組み合わせることができます。Cコンパイラ自体には、文字列に関する知識があります。ただし、この情報は実行時に存在しないため、連結は発生しません。

コンパイルプロセス中、最初の例は次のように「変換」されます。

int main() {
    static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
    printf(char_ptr_1);
}

プログラムが実行される前に、コンパイラーによって2つのストリングが単一の静的配列に結合される方法に注意してください。

ただし、2番目の例は次のように「変換」されます。

int main() {
    static const char char_ptr_1[] = {'H', 'i', '\0'};
    static const char char_ptr_2[] = {'B', 'y', 'e', '\0'};
    static const char char_ptr_3[] = {'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0'};
    int test = 0;
    printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));
}

これがコンパイルされない理由は明らかです。3項演算子?は、「文字列」が存在しなくなったときに、コンパイル時にではなく実行時に評価されますが、ポインタcharによって参照される単純な配列としてのみ評価されchar*ます。隣接する文字列リテラルとは異なり、隣接するcharポインタは単なる構文エラーです。


2
すばらしい答えです。おそらくここで最高です。「これがコンパイルされない理由は明らかです。」「三項演算子はコンパイル時ではなく実行時に評価されるので」でそれを拡張することを検討するかもしれません。

残りのポインタについても同様にすべきではstatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};ありませんstatic const char *char_ptr_1 = "HiBye";か?
Spikatrix 2016年

@CoolGuy作成するとstatic const char *char_ptr_1 = "HiBye";、コンパイラはその行をstatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};に変換します。そのため、「文字列のように」書かれるべきではありません。回答にあるように、文字列は文字の配列にコンパイルされます。文字列を最も「未加工の」形式で割り当てる場合は、次のように、カンマ区切りの文字リストを使用しますstatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush

3
@Ankushはい。が、しかし、static const char str[] = {'t', 'e', 's', 't', '\0'};同じであるstatic const char str[] = "test";static const char* ptr = "test";はないと同じstatic const char* ptr = {'t', 'e', 's', 't', '\0'};。前者は有効でコンパイルされますが、後者は無効であり、期待どおりに動作します。
Spikatrix 2016年

最後の段落を具体化し、コード例を修正しました。ありがとうございます。
署名なし2016年

12

両方のブランチで実行時に選択されるコンパイル時の文字列定数を生成したい場合は、マクロが必要です。

#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))

int
main ( int argc, char **argv){
  printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
  return 0;
}

10

その理由は何ですか?

三項演算子を使用するコードは、条件付きで2つの文字列リテラルを選択します。既知または未知の条件に関係なく、これはコンパイル時に評価できないため、コンパイルできません。このステートメントでもprintf("Hi" (1 ? "Bye" : "Goodbye"));コンパイルされません。その理由は、上記の回答で詳しく説明されています。三項演算子を使用してそのようなステートメントをコンパイル有効にする別の可能性には、フォーマットタグと、への追加の引数としてフォーマットされた三項演算子ステートメントの結果も含まれprintfます。それでも、printf()印刷出力は、実行時だけで、実行時と同じくらい早くこれらの文字列を「連結した」ような印象を与えます

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}

3
SOはチュートリアルサイトではありません。チュートリアルではなく、OPに回答してください。
ミチ

1
これはOPの質問には答えません。これは、OPの根本的な問題を解決する試みである可能性がありますが、それが何であるかは実際にはわかりません。
キーストンプソン、

1
printfフォーマット指定子は必要ありません。コンパイル時に連結のみが行われた場合(そうではありません)、OPによるprintfの使用は有効です。
デビッドコンラッド

@David Conrad、発言ありがとうございます。私のずさんな言い回しは確かに、あたかもprintf()それが絶対に真実ではないフォーマットタグを必要とするかのように見えるようにする でしょう。修正しました!
user3078414

それはより良い言い回しです。+1ありがとうございます。
デビッドコンラッド

7

printf("Hi" "Bye");、あなたは、コンパイラは、単一のアレイに作ることができるのcharの二つの連続配列を持っています。

printf("Hi" (test ? "Bye" : "Goodbye"));は、1つの配列の後にcharへのポインタが続きます(最初の要素へのポインタに変換された配列)。コンパイラーは配列とポインターをマージできません。


0

質問に答えるために-私はprintfの定義に行きます。関数printfは、引数としてconst char *を想定しています。「Hi」などの文字列リテラルはconst char *です。ただし、(test)? "str1" : "str2"などの式はconst char *ではありません。そのような式の結果は実行時にのみ検出され、コンパイル時に不確定になるため、コンパイラーが正しく文句を言うためです。一方、これは完璧に機能しますprintf("hi %s", test? "yes":"no")


*ただし、このような表現(test)? "str1" : "str2"const char*...ではありません...もちろんです。定数式ではありませんが、型 const char *です。書くのはまったく問題ありませんprintf(test ? "hi " "yes" : "hi " "no")。OPの問題は、とは何の関係もないprintf"Hi" (test ? "Bye" : "Goodbye")関係なく表現コンテキストが何であるか構文エラーではありません。
chqrlie

同意した。式の出力と式自体を混同しました
Stats_Lover

-4

printf関数のパラメーターリストが

(const char *format, ...)

そして

("Hi" (test ? "Bye" : "Goodbye"))

パラメータリストに適合しません。

gccはそれを想像することでそれを理解しようとします

(test ? "Bye" : "Goodbye")

パラメータリストであり、「Hi」は関数ではないと不平を言う。


6
Stack Overflowへようこそ。printf()引数リストと一致しないのは正しいですが、それは、式がprintf()引数リストだけでなく、どこでも有効でないためです。言い換えれば、あなたは問題に対してあまりに特殊化した理由を選んだのです。一般的な問題は"Hi" (、Cでは無効であり、への呼び出しでは無効ですprintf()。反対票を投じる前に、この回答を削除することをお勧めします。
ジョナサンレフラー、2016年

それはCが動作する方法ではありません。これは、PHPのような文字列リテラルを呼び出そうとして解析されません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.