web-dev-qa-db-ja.com

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

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

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

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

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

その理由は何ですか?

95
José D.

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

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

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

そしてその後だけ

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

この構造では

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

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

119

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

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

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

_printf("Hi" (test ? "Bye" : "Goodbye"));
_

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


why部分を少し詳しく説明するために、前処理段階で、隣接する文字列リテラルが連結され、単一として表現されますstring literal(トークン)。ストレージはそれに応じて割り当てられ、連結された文字列リテラルはsingle entity(1つの文字列リテラル)と見なされます。

一方、実行時の連結の場合、宛先には連結されたstring literalを保持するのに十分なメモリが必要です。そうでなければ、expected連結された出力にアクセスできます。現在、string literalsの場合、これらはコンパイル時にすでにメモリが割り当てられており、extendedにはできません。それ以上の着信入力に適合するintoまたはappended to元のコンテンツ。つまり、連結された結果に単一のstring literalとしてアクセス(提示)する方法はありません。したがって、この構造は本質的に正しくありません。

参考までに、実行時の string not literals)連結には、ライブラリ関数 strcat() 2つのstringsを連結します。注意してください、説明は言及しています:

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

strcat()関数は、_s2_が指す文字列のコピーを、_s1_が指す文字列の末尾に追加します(終端のヌル文字を含む) 。 _s2_の最初の文字は、_s1_の最後のヌル文字を上書きします。 [...]

したがって、_s1_は string ではなく string であることがわかります。ただし、_s2_の内容はまったく変更されないため、string literalになります。

134
Sourav Ghosh

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

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

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

Cにはstringタイプがないためです。文字列リテラルは、char*ポインターによって参照されるchar配列にコンパイルされます。

Cでは、最初の例のように、隣接するliteralsコンパイル時に結合できます。 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));
}

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

30
Unsigned

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

#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;
}
12
Eric

その理由は何ですか?

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

#include <stdio.h>

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

printf("Hi" "Bye");には、コンパイラーが単一の配列にできるcharの2つの連続した配列があります。

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

7
pmg

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

0
Stats_Lover