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


95

下面的代码编译没有问题:

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

但是,这不能编译:

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

是什么原因呢?


95
字符串连接是早期词汇化阶段的一部分;它不是C表达式synatx的一部分。换句话说,没有“字符串文字”类型的。相反,字符串文字是构成值的源代码的词汇元素。
Kerrek SB

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 '16

4
注意,即使printf("Hi" ("Bye"));不起作用,也不需要三元运算符。括号就足够了(尽管printf("Hi" test ? "Bye" : "Goodbye")也不会编译)。字符串字面量后面只能有数量有限的令牌。逗号,,右方括号[,右方括号](如1["abc"]—是的,这是令人讨厌的),右方括号),右方花括号}(在初始化程序或类似上下文中)和分号;是合法的(以及另一个字符串文字);我不确定还有其他人。
乔纳森·勒夫勒

Answers:


121

根据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"呢?(我的问题在其他答案中得到了回答)
疯狂的2016年

48
@LightnessRacesinOrbit,因为当人们通常问为什么某些内容不能在C中编译时,他们是在要求澄清它违反了哪个规则,而不是为什么上古标准作者选择了这种方式。
user1717828

4
@LightnessRacesinOrbit您描述的问题可能不在主题之列。我看不到没有任何技术原因无法实现该目标,因此,如果没有规范作者的明确答案,所有答案都将基于观点。而且通常不会属于“实用”或“可回答”问题的类别(如帮助中心指示我们需要的那样)。
jpmc26 2013年

12
@LightnessRacesinOrbit这说明了原因:“因为C标准这么说”。关于为什么要按定义定义此规则的问题将成为主题。
user11153 '16

135

根据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未作任何更改,因此很可能是字符串文字


您可能要添加有关以下内容的额外说明strcat:目标数组必须足够长,才能从s2已经存在的字符后接收来自加上一个空终止符的字符。
chqrlie

39

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

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

但是,即使没有以这种方式表达此限制,或者该限制的构造方式不同,如果不将并置作为运行时过程,您的示例仍将无法实现。而且,为此,我们具有诸如的库函数strcat


3
我只是阅读假设。尽管您说的很合法,但您没有提供任何资料来源。关于C的唯一来源是标准文档(尽管在许多情况下是显而易见的),它没有说明为什么某些事物是它们的样子,而只是指出它们必须是这种特定的样子。因此,从莫斯科的回答中对弗拉德一无所知是不恰当的。由于OP可以细分为“为什么这样?” -唯一正确来源的答案是“因为它是C,这就是C的定义方式”,这是唯一从字面上讲正确的正确答案。
dhein '16

1
(承认)缺乏解释。但是,在这里蜜蜂再次说,弗拉德的回答比起您的回答,更能解释核心问题。再次说:您提供的我可以确认的信息是相关且正确的,但我不同意您的投诉。虽然我也不会考虑您的异位症,但从我的观点来看,它比Vlads实际的异位症更多。
dhein '16

11
@Zaibis:消息来源是我。弗拉德的回答根本不是解释。这仅仅是对问题前提的确认。当然,它们都不是“题外话”(您可能想查询该术语的含义)。但是您有权发表自己的意见。
Lightness Races in Orbit

即使阅读了以上评论,我仍然想知道是谁否决了这个答案ᵒᴥᵒᶅᵒᴥᵒᶅ除非OP要求对此答案进行进一步的澄清,否则我相信这是一个完美的答案。
Mohit Jain

2
我无法区分为什么这个答案是您可以接受的,而@VladfromMoscow的答案是不可接受的,当他们两个都说相同的话,而他的引用被支持而您的却不被接受时。
罗恩侯爵,

30

因为C没有string类型。字符串文字被编译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));
}

应该清楚为什么不能编译。?当“字符串”不再这样存在,而是仅作为charchar*指针引用的简单数组存在时,将在运行时而不是编译时评估三元运算符。与相邻的字符串文字不同,相邻的char指针只是语法错误。


2
很好的答案,可能是最好的答案。“应该清楚为什么不能编译。” 您可能会考虑用“因为三元运算符是在运行时而不是编译时评估的条件”来扩展它。

对于其余的指针,不static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};应该static const char *char_ptr_1 = "HiBye";并且类似吗?
Spikatrix '16

@CoolGuy在编写static const char *char_ptr_1 = "HiBye";编译器时static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};,会将行转换为,所以不,不应“像字符串一样”编写它。就像答案中所说的那样,字符串被编译为一个char数组,如果您以最原始的形式分配char数组,则将使用逗号分隔的char列表,​​就像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年

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

是什么原因呢?

使用三元运算符的代码有条件地在两个字符串文字之间进行选择。无论条件是已知的还是未知的,都无法在编译时进行评估,因此无法编译。即使该语句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一个答案,而不是一个教程。
Michi

1
这不能回答OP的问题。这可能是解决OP潜在问题的尝试,但是我们真的不知道那是什么。
基思·汤普森

1
printf并不需要一个格式说明; 如果只有串联是在编译时完成的(不是这样),则OP对printf的使用将是有效的。
戴维·康拉德

感谢您的发言,@ David Conrad。我的草率措词确实会使人看起来好像在声明 printf()将需要格式标签一样,这绝对是不正确的。更正!
user3078414

这是一个更好的措辞。+1谢谢。
戴维·康拉德

7

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

在其中printf("Hi" (test ? "Bye" : "Goodbye"));,有一个数组,后跟指向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"))

不适合参数列表。

海湾合作委员会试图通过想象一下使它有意义

(test ? "Bye" : "Goodbye")

是参数列表,并且抱怨“ Hi”不是函数。


6
欢迎使用堆栈溢出。没错,它与printf()参数列表不匹配,但这是因为表达式在任何地方都无效-不仅在printf()参数列表中。换句话说,您已经选择了一个太过专业的原因来解决该问题。一般的问题是"Hi" (在C语言中无效,更不用说对的调用了printf()。我建议您在投票否决之前删除此答案。
乔纳森·莱夫勒

这不是C的工作方式。不能将其解析为试图调用类似PHP的字符串文字。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.