我=(i,++ i,1)+1; 做?

在阅读了关于未定义的行为和顺序点的答案后,我写了一个小程序:

#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:警告:逗号expression式的左侧操作数不起作用

  [-Wunused-value] i = (i, ++i, 1) + 1; ^ 

为什么? 但可能会自动回答我的第一个问题的答案。

在expression式(i, ++i, 1) ,逗号使用的是逗号运算符

逗号运算符(由令牌表示)是一个二元运算符,它评估其第一个操作数并放弃结果,然后评估第二个操作数并返回该值(和types)。

因为它放弃了它的第一个操作数,所以通常只在第一个操作数具有理想的副作用时才有用 。 如果第一个操作数的副作用没有发生,那么编译器可能会产生一个关于expression式的警告而不起作用。

所以,在上面的expression式中,最左边的i将被评估,其值将被丢弃。 然后++i将被评估,并将i 1递增,再次expression式++i的价值将被丢弃, 但对i的副作用是永久性的 。 然后1将被评估,expression式的值将是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; 

请注意, 上述expression式是完全有效的,并且不会调用未定义的行为,因为在逗号运算符的左侧和右侧操作数的评估之间存在序列点 。

逗号运算符C116.5.17章引用

逗号运算符的左操作数被评估为voidexpression式; 它的评估与右操作数的评估之间有一个顺序点。 然后评估右操作数; 结果有它的types和价值。

所以,在你的情况下,

 (i, ++i, 1) 

被评估为

  1. i ,被评估为一个无效的expression,价值被丢弃
  2. ++i被评估为一个voidexpression式,值被丢弃
  3. 最后, 1 ,价值返回。

所以,最后的声明看起来像

 i = 1 + 1; 

i得到2 。 我想这回答你的两个问题,

  • 我如何获得价值2?
  • 为什么有一个警告信息?

注意:由于在左手操作数的评估之后出现了一个序列点 ,所以像(i, ++i, 1)这样的expression式不会调用UB,正如人们通常认为的那样。

 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, ++i, 1) 

 1 

对于

 (i,++i,1) 

评估发生时,操作员丢弃评估值并将保留最右边的值1

所以

 i = 1 + 1 = 2 

您会在逗号运算符的wiki页面上find一些很好的阅读。

基本上,它

…评估其第一个操作数并放弃结果,然后评估第二个操作数并返回该值(和types)。

这意味着

 (i, i++, 1) 

依次评估i ,放弃结果,评估i++ ,放弃结果,然后评估并返回1

你需要知道逗号操作符在这里做什么:

你的表情:

 (i, ++i, 1) 

评估第一个expression式i ,评估第二个expression式++i ,并且为整个expression式返回第三个expression式1

所以结果是: i = 1 + 1

正如你所看到的,对于你的奖金问题,第一个expression式i根本没有任何作用,所以编译器会抱怨。

逗号有一个“反”优先级。 这是您将从旧书和IBM的手册(70年代/ 80年代)中获得的。 所以最后的'命令'是在父expression式中使用的。

在现代C中,它的使用是奇怪的,但在旧的C(ANSI)中是非常有趣的:

 do { /* bla bla bla, consider conditional flow with several continue's */ } while ( prepAnything(), doSomethingElse(), logic_operation); 

虽然所有操作(函数)都是从左到右调用的,但只有最后一个expression式才会被用作条件“while”的结果。 这样可以防止goto在条件检查之前保持一个唯一的命令块运行。

编辑:这也避免了一个处理函数的调用,它可以处理左操作数的所有逻辑,并返回逻辑结果。 请记住,我们在C的过去没有内联函数。因此,这可以避免调用开销。