为什么在使用条件操作符时,C语言不允许连接字符串?

101
下面的代码没有问题地编译通过:
int main() {
    printf("Hi" "Bye");
}

然而,这个代码不能编译:

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}
那个原因是什么?

95
字符串拼接是早期词法分析阶段的一部分,它不属于C语言表达式语法的一部分。换句话说,“字符串字面值”的类型并没有对应的。相反,字符串字面值是源代码的词法元素,形成值。 - Kerrek SB
24
仅供澄清@KerrekSB的答案 - 字符串的连接是代码文本在编译之前的预处理部分。而三元运算符在运行时计算,在代码编译后(或者在所有内容都是常量的情况下可以在编译时完成)。 - Eugene Sh.
2
在本文中,"Hi""Bye"是字符串字面量,而不是C标准库中使用的字符串。使用字符串字面量,编译器将连接"H\0i" "B\0ye"。但是sprintf(buf,"%s%s", "H\0i" "B\0ye");则不同。 - chux - Reinstate Monica
15
由于大致相同的原因,您无法执行a (some_condition ? + : - ) b - user253751
5
请注意,即使是 printf("Hi" ("Bye")); 也不起作用——它不需要三元运算符;括号就足够了(尽管 printf("Hi" test ? "Bye" : "Goodbye") 也无法编译)。只有有限数量的标记可以跟随字符串字面值。逗号 ,、开方括号 [、闭方括号 ](如 1["abc"] ——是可怕的),闭圆括号 )、闭花括号 }(在初始化程序或类似上下文中),以及分号 ; 是合法的(和另一个字符串字面值);我不确定是否还有其他的。 - Jonathan Leffler
显示剩余13条评论
9个回答

137
根据C11标准,第§5.1.1.2章节,相邻字符串文字的连接:相邻字符串文字会被连接起来。这发生在翻译阶段。另一方面:
printf("Hi" (test ? "Bye" : "Goodbye"));

涉及到条件运算符,该运算符在运行时被评估。因此,在编译时,在翻译阶段期间,不存在相邻的字符串字面量,因此无法进行串联。语法无效,因此由编译器报告。
稍微详细解释一下为什么,在预处理阶段,相邻的字符串字面量被连接并表示为单个字符串字面量(标记)。相应地分配存储空间,并将连接的字符串字面量视为单个实体(一个字符串字面量)。
另一方面,在运行时串联的情况下,目标应具有足够的内存来容纳连接的字符串字面量,否则将无法访问预期的连接输出。现在,在字符串字面量的情况下,它们已经在编译时分配了内存,并且不能扩展以适应任何更多的输入进入附加到原始内容。换句话说,没有办法将连接的结果表示为单个字符串字面量。因此,这种构造本质上是不正确的。
只是为了提供信息,对于运行时字符串不是字面量)串联,我们有库函数strcat(),它将两个字符串连接起来。请注意,描述中提到:

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

strcat()函数将指向s2指向的字符串(包括结尾的空字符)的副本附加到指向s1字符串的末尾s2的初始字符覆盖s1末尾的空字符。[...]

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

1
你可能需要对strcat进行额外的解释:目标数组必须足够长,以接收s2中的字符以及已经存在的字符后面的空终止符。 - chqrlie

123

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

1 翻译中语法规则的优先级由以下阶段指定。6)

  1. 相邻的字符串字面量记号被连接。

仅在此之后

  1. 分隔标记的空格字符不再具有意义。每个预处理记号都转换为一个记号。生成的记号作为翻译单元进行语法和语义分析并翻译

在这个结构中

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

没有相邻的字符串字面量标记,因此这个结构是无效的。


43
这只是重申了在C语言中不被允许的说法,而没有解释为什么,这也是问题所在。我不知道为什么它在5个小时内就获得了26个赞......而且还被采纳了!恭喜。 - Lightness Races in Orbit
4
同意@LightnessRacesinOrbit的说法。为什么(test ? "Bye" : "Goodbye")不能评估为任何一个字符串字面值,从而使得"Hi" "Bye"或者"Hi Goodbye"呢?(我的问题已经在其他答案中得到了解答) - Insane
49
@LightnessRacesinOrbit,因为当人们通常询问为什么某个C程序无法编译时,他们是在询问它违反了哪个规则,而不是为什么Standards Authors of Antiquity选择了这种方式。 - user1717828
4
@LightnessRacesinOrbit你提到的问题可能不符合主题。我看不出为什么不能实现这个问题,因此如果没有规范作者的明确答案,所有回答都是基于个人意见的。这一般不属于“实用”或“可回答”的问题类别(正如[帮助/不要问]所指示的那样)。 - jpmc26
12
它解释了为什么:“因为C标准如此规定”。关于为什么定义这个规则的问题将属于离题。 - user11153
显示剩余17条评论

41

字符串字面量的拼接是在编译时由预处理器执行的。这种拼接无法意识到test的值,因为该值直到程序实际执行时才能确定。因此,这些字符串字面量无法被连接。

因为通常情况下,你不会在编译时就知道需要拼接哪些值,所以C标准将自动拼接功能限制为最基本的情况:当字面量直接相邻时。

但即使它没有以那种方式表达这种限制,或者如果限制是不同构造的,你的示例仍然无法实现,除非将拼接过程变成运行时的。为此,我们有像strcat这样的库函数。


4
我刚读了你的假设。虽然你说的大部分是正确的,但由于没有来源,你无法提供支持。关于C语言的唯一来源是标准文档,这份文档虽然在许多情况下很明显,但并没有说明为什么有些事情会是这样,只是说明它们必须以特定的方式进行。因此,对莫斯科弗拉德的答案如此挑剔是不合适的。由于OP可以被分解为“为什么会是这样?”-其中唯一正确的来源答案是“因为这是C语言,并且C语言被定义为这种方式”,这是唯一的文字上正确的答案。 - dhein
1
这段内容(承认)缺乏解释。但是再次强调,Vlad的回答更像是对核心问题的解释。再次说:虽然我可以确认你提供的信息是相关和正确的,但我不同意你的抱怨。虽然我也不认为你的话题离题,但从我的角度来看,它比Vlad的更离题。 - dhein
11
@Zaibis说:“资料来源是我。Vlad的回答根本不是解释,只是确认问题前提的正确性。当然,他们两个都不是“离题”的(你可能需要查一下这个术语的含义)。但你有权发表自己的观点。” - Lightness Races in Orbit
即使阅读了上面的评论,我仍然想知道是谁对这个答案进行了负评 ᶘ ᵒᴥᵒᶅ 我认为这是一个完美的答案,除非楼主要求进一步澄清此答案。 - Mohit Jain
2
我无法区分为什么您认为这个答案是可接受的,而@VladfromMoscow的答案不可接受,因为他的答案有引用,而您的没有,但它们都说了同样的事情。 - user207421
1
@EJP:天哪,这又来了。他们并不是说同样的事情。他的重复了问题的前提,而我的回答了它。对这两段文本进行比较,你会发现它们非常不同。 - Lightness Races in Orbit

31

由于C语言没有字符串类型,字符串字面量被编译为char数组,并由char*指针引用。

C语言允许在编译时将相邻的字面量组合在一起,就像你的第一个示例一样。 C编译器本身对字符串有一些了解。 但是这些信息在运行时不可用,因此无法进行字符串连接。

在编译过程中,你的第一个示例被“转换”为:

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

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

然而,你的第二个示例被“翻译”成类似于以下内容:

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 数组存在。相邻的 字符串字面量 不同于相邻的 字符指针,后者只是语法错误。

2
非常好的答案,可能是这里最好的答案。“这不编译应该很清楚。”您可以考虑扩展一下,“因为三元运算符是在运行时而不是编译时评估的条件语句”。 - cat
static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'}; 这句话不应该是 static const char *char_ptr_1 = "HiBye"; 吗?其他指针也应该类似。 - Spikatrix
@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
是的。但是尽管 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
我已经完善了最后一段并更正了代码示例,谢谢! - Unsigned

14

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

#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

为什么会这样呢?

您的代码使用三元运算符在两个字符串文本之间进行有条件选择。无论条件是已知还是未知,都无法在编译时评估,因此无法编译。即使此语句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不是一个教程网站。你应该向提问者提供答案,而不是教程。 - Michi
1
这并没有回答楼主的问题。这可能是解决楼主潜在问题的尝试,但我们并不真正知道那是什么。 - Keith Thompson
1
printf需要格式说明符;如果只有在编译时进行连接(实际上并不是这样),那么 OP 使用 printf 的方式就是有效的。 - David Conrad
感谢您的评论,@David Conrad。我的措辞不够严谨,似乎在表明 printf() 需要一个格式标签,这是绝对不正确的。已经更正! - user3078414
那个措辞更好。+1 谢谢。 - David Conrad

7
printf("Hi" "Bye");中,你有两个连续的字符数组,编译器可以将它们合并成一个单独的数组。
printf("Hi" (test ? "Bye" : "Goodbye"));中,你有一个数组后面跟着一个指向字符的指针(将一个数组转换为指向其第一个元素的指针)。编译器无法合并一个数组和一个指针。

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() 的调用了。我建议在被踩之前删除这个答案。 - Jonathan Leffler
这不是 C 语言的工作方式。这不会像 PHP 一样尝试调用字符串字面量进行解析。 - cat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接