I = (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。哦,上帝,我没有看到递减来了! 这里发生了什么?

另外,在编译上述代码时,我收到一个警告:

C: 5:8: 警告: 逗号表达式的左操作数没有效果

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

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

17709 次浏览

结果

(i, ++i, 1)

1

为了

(i,++i,1)

计算发生时,,运算符丢弃计算的值,只保留正确的最大值 1

那么

i = 1 + 1 = 2

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

逗号操作符(由令牌 ,表示)是一个二进制操作符,它计算第一个操作数并丢弃结果,然后计算第二个操作数并返回这个值(和类型)。

因为它丢弃了它的第一个操作数,所以它通常是 只有在第一个操作数有理想的副作用时才有用。如果第一个操作数的副作用没有发生,那么编译器可能会生成关于表达式的警告,但没有效果。

因此,在上面的表达式中,将计算最左边的 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;

注意,上述表达式是完全正确的,不会引起未定义行为是因为在逗号运算符的左右操作数的求值之间有一个 序列点

你会在维基页面上找到一些关于 逗号运算符的好的阅读资料。

基本上

... 计算其第一个操作数并放弃结果,然后计算第二个操作数并返回此值(并键入)。

这意味着

(i, i++, 1)

将依次计算 i,丢弃结果,计算 i++,丢弃结果,然后计算并返回 1

引自 C116.5.17章,逗号运算符

逗号运算符的左操作数作为 void 表达式计算; 有一个 它的计算和右操作数的计算之间的序列点。然后右 操作数; 结果有其类型和值。

对你来说,

(i, ++i, 1)

被评估为

  1. i,作为 void 表达式计算,值丢弃
  2. ++i,作为 void 表达式计算,值丢弃
  3. 最后,返回值 1

最后一个声明看起来像

i = 1 + 1;

然后 i2我想这回答了你们两个的问题,

  • i如何得到值2?
  • 为什么会有警告信息?

注意: FWIW,因为在左操作数求值之后存在一个 序列点,所以像 (i, ++i, 1)这样的表达式不会调用 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)

计算第一个表达式 i,第二个表达式 ++i,并为整个表达式返回第三个表达式 1

所以结果是: i = 1 + 1

正如您所看到的,对于额外的问题,第一个表达式 i根本没有任何效果,因此编译器会抱怨。

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

在现代 C 语言中,它的用法很奇怪,但在旧 C 语言(ANSI)中却很有趣:

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

虽然所有的操作(函数)都是从左到右调用的,但是只有最后一个表达式将作为条件“ While”的结果使用。 这阻止了对‘ goto’的处理,以便在条件检查之前保持一个独特的命令块来运行。

编辑: 这也避免了对处理函数的调用,该函数可以处理左操作数上的所有逻辑,从而返回逻辑结果。请记住,我们在过去的 C 语言中没有内联函数,因此,这可以避免调用开销。