为什么 C 在使用条件运算符时不允许连接字符串?

Why does C not allow concatenating strings when using the conditional operator?

以下代码编译没有问题:

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

但是,这不会编译:

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

这是什么原因?

根据 C 标准(5.1.1.2 翻译阶段)

1 The precedence among the syntax rules of translation is specified by the following phases.6)

  1. Adjacent string literal tokens are concatenated.

只有在那之后

  1. White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. The resulting tokens are syntactically and semantically analyzed and translated as a translation unit.

在此构造中

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

没有相邻的字符串文字标记。所以这个构造是无效的。

根据 C11 标准,章节 §5.1.1.2,相邻字符串文字的连接:

Adjacent string literal tokens are concatenated.

发生在翻译阶段。另一方面:

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

涉及条件运算符,在运行时计算。因此,在编译时,在翻译阶段,不存在相邻的字符串文字,因此连接是不可能的。语法无效,因此由您的编译器报告。


为了详细说明 为什么 部分,在预处理阶段,相邻的字符串文字被连接起来并表示为单个字符串文字(标记)。相应地分配存储空间,连接的字符串文字被视为 单个实体 (一个字符串文字)。

另一方面,在 运行 次连接的情况下,目的地应该有足够的内存来容纳连接的 字符串文字 否则,将没有可以访问 expected 串联输出的方式。现在,在 字符串文字 的情况下,它们已经在编译时分配了内存并且不能 扩展 以适应任何更多的传入输入 进入附加到原始内容。换句话说,无法将连接的结果作为单个 字符串文字 访问(呈现)。所以,这个构造本质上是不正确的。

仅供参考,对于 运行-time stringnot literals)连接,我们有库函数 strcat() 连接两个 字符串 。注意,描述中提到:

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

The strcat() function appends a copy of the string pointed to by s2 (including the terminating null character) to the end of the string pointed to by s1. The initial character of s2 overwrites the null character at the end of s1. [...]

因此,我们可以看到,s1 是一个 字符串,而不是 字符串文字 。但是,由于 s2 的内容没有任何改变,它很可能是 字符串文字 .

What is the reason for that?

您使用三元运算符的代码有条件地在两个字符串文字之间进行选择。无论条件已知还是未知,这都无法在编译时进行评估,因此无法编译。即使这条语句 printf("Hi" (1 ? "Bye" : "Goodbye")); 也无法编译。原因在上面的答案中有深入的解释。 使用对编译有效的三元运算符制作这样的语句的另一种可能性,也将涉及一个格式标签和格式化为三元运算符语句的结果 printf 的附加参数 。即便如此,printf() 打印输出只会给人留下 "having concatenated" 这些字符串的印象,而且早在 runtime.

#include <stdio.h>

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

字符串文字连接由预处理器在编译时执行。这种连接无法知道 test 的值,直到程序实际执行时才知道。因此,这些字符串文字不能连接。

因为一般情况下您不会对编译时已知的值进行这样的构造,所以 C 标准旨在将自动串联功能限制在最基本的情况下:当文字是字面上并排。

但是,即使它没有以这种方式表述此限制,或者如果限制的构造不同,如果不将串联作为运行时进程,您的示例仍然无法实现。而且,为此,我们有库函数,例如 strcat.

因为 C 没有 string 类型。字符串文字被编译为 char 数组,由 char* 指针引用。

C 允许在编译时 合并相邻的 文字 ,如您的第一个示例。 C 编译器本身对字符串有一些了解。但是此信息在运行时不存在,因此无法进行连接。

在编译过程中,你的第一个例子是"translated"到:

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

请注意编译器如何在程序执行之前将两个字符串组合成一个静态数组。

但是,您的第二个示例是 "translated" 类似这样的内容:

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

应该清楚为什么这不能编译。三元运算符 ? 在运行时计算,而不是编译时,此时 "strings" 不再存在,而仅作为简单的 char 数组,由 char* 指针引用.与相邻的 字符串文字 不同,相邻的 字符指针 只是一个语法错误。

如果您真的想让两个分支都生成编译时字符串常量以在运行时选择,您将需要一个宏。

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

printf("Hi" "Bye"); 中,您有两个连续的 char 数组,编译器可以将其变成一个数组。

printf("Hi" (test ? "Bye" : "Goodbye")); 中,您有一个数组,后跟一个指向 char 的指针(一个数组转换为指向其第一个元素的指针)。编译器无法合并 数组和指针。

这不会编译,因为 printf 函数的参数列表是

(const char *format, ...)

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

不符合参数列表。

gcc 试图通过想象来理解它

(test ? "Bye" : "Goodbye")

是参数列表,抱怨"Hi"不是函数。

为了回答这个问题 - 我将转到 printf 的定义。函数 printf 需要 const char* 作为参数。任何字符串文字如 "Hi" 都是 const char*;然而,诸如 (test)? "str1" : "str2" 之类的表达式不是 const char*,因为此类表达式的结果仅在 运行 时被发现,因此在编译时是不确定的,这一事实会导致编译器抱怨.另一方面 - 这非常有效 printf("hi %s", test? "yes":"no")