在阅读this answer关于未定义的行为和序列点之后,我写了一个小程序:
#include <stdio.h>
int main(void) {
int i = 5;
i = (i, ++i, 1) + 1;
printf("%d\n", i);
return 0;
}
输出为2
。天啊,我没有看到减量来了!这里发生了什么?
另外,在编译上面的代码时,我收到了警告:
px.c:5:8:警告:逗号表达式的左侧操作数无效
[-Wunused-value] i = (i, ++i, 1) + 1; ^
为什么呢?但可能会在第一个问题的答案中自动回答。
答案 0 :(得分:256)
在表达式(i, ++i, 1)
中,使用的逗号是comma operator
逗号运算符(由标记
,
表示)是一个二元运算符,它计算第一个操作数并丢弃结果,然后计算第二个操作数并返回该值(和类型)。
因为它丢弃了它的第一个操作数,所以通常仅在第一个操作数具有所需副作用的情况下才有用。如果没有发生对第一个操作数的副作用,则编译器可能会生成有关表达式的警告而不起作用。
因此,在上面的表达式中,将评估最左边的i
,并且将丢弃其值。然后将++i
进行评估,并将i
增加1,同时表达式++i
的值将被丢弃,但i
的副作用是永久性的。然后将评估1
,表达式的值将为1
。
相当于
i; // Evaluate i and discard its value. This has no effect.
++i; // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;
请注意,上述表达式完全有效且不会调用未定义的行为,因为在逗号运算符的左右操作数的求值之间存在sequence point。
答案 1 :(得分:62)
引自C11
,章6.5.17
,Comma operator
逗号运算符的左操作数被计算为void表达式;有一个 其评估与右操作数之间的序列点。然后是正确的 操作数被评估;结果有其类型和价值。
所以,在你的情况下,
(i, ++i, 1)
评估为
i
,被评估为void表达式,值被丢弃++i
,被评估为void表达式,值被丢弃1
,返回值。所以,最后的陈述看起来像是
i = 1 + 1;
和i
到达2
。我想这可以解答你的两个问题,
i
如何获得值2?注意:FWIW,因为在评估左手操作数后存在序列点,像(i, ++i, 1)
这样的表达式不会调用UB,作为一个可能通常会误认为。
答案 2 :(得分:30)
i = (i, ++i, 1) + 1;
让我们一步一步地分析它。
(i, // is evaluated but ignored, there are other expressions after comma
++i, // i is updated but the resulting value is ignored too
1) // this value is finally used
+ 1 // 1 is added to the previous value 1
所以我们获得2.现在最后的任务:
i = 2;
i 中的任何内容现在都被覆盖了。
答案 3 :(得分:19)
的结果
(i, ++i, 1)
是
1
有关
(i,++i,1)
评估发生时,,
运算符会丢弃评估值,并保留最正确的值1
所以
i = 1 + 1 = 2
答案 4 :(得分:14)
你会在Comma operator的维基页面上找到一些好的阅读。
基本上,它
...计算其第一个操作数并丢弃结果,然后计算第二个操作数并返回该值(并键入)。
这意味着
(i, i++, 1)
反过来,会评估i
,放弃结果,评估i++
,弃掉结果,然后评估并返回1
。
答案 5 :(得分:13)
您需要知道逗号运算符在这里做了什么:
你的表达:
(i, ++i, 1)
评估第一个表达式i
,计算第二个表达式++i
,并为整个表达式返回第三个表达式1
。
结果是:i = 1 + 1
。
对于你的奖金问题,如你所见,第一个表达式i
根本没有效果,所以编译器会抱怨。
答案 6 :(得分:5)
逗号有一个反向的&#39;优先。这是您从IBM(70年代/ 80年代)的旧书和C手册中获得的内容。所以最后一个命令&#39;是父表达式中使用的。
在现代C中,它的使用很奇怪,但在旧的C(ANSI)中非常有趣:
do {
/* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);
虽然从左到右调用所有操作(函数),但只有最后一个表达式将被用作条件&#39;而#39;。 这样可以防止处理“goto”以在条件检查之前保留一个唯一的命令块。
编辑:这也避免了对处理函数的调用,该处理函数可以处理左操作数的所有逻辑,因此返回逻辑结果。请记住,我们在C的过去没有内联函数。因此,这可以避免调用开销。