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


174

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

#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;
                        ^

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


288
不要做奇怪的事情,您将没有朋友:(
Maroun

9
警告消息是您第一个问题的答案。
于浩

2
@gsamaras:不。结果值将被丢弃,而不是修改。真正的答案:逗号运算符创建一个序列点。
Karoly Horvath

3
@gsamaras当您的分数为正数时,甚至10个以上的问题时,您都不在乎。
LyingOnTheSky

9
注意:优化的编译器可能很简单printf("2\n");
chux-恢复Monica 2015年

Answers:


256

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

逗号运算符(由标记表示,)是一个二进制运算符,它评估其第一个操作数并丢弃结果,然后评估第二个操作数并返回此值(和类型)。

因为它丢弃其第一个操作数,所以通常仅在第一个操作数具有所需副作用的情况下才有用。如果未对第一个操作数产生副作用,则编译器可能会生成有关该表达式的警告,但不会生效。

因此,在上面的表达式中,i将评估最左边的内容并将其值丢弃。然后++i将被求值并将其递增i1,并再次++i舍弃表达式的值,但对to的副作用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;  

请注意,上面的表达式是完全有效的,并且不会调用未定义的行为,因为在逗号运算符的左操作数和右操作数的求之间存在一个序列点


1
尽管最后一个表达式是有效的,但是第二个表达式++ i不是未定义的行为吗?它被评估并且未初始化变量的值被预先增加,这是不对的?还是我错过了什么?
Koushik Shetty

2
@Koushik; i用初始化5。查看声明声明int i = 5;

1
哦,我的坏。对不起,老实说,我看到了。
Koushik Shetty

这里有一个错误:++ i将对i进行递增然后对其求值,而i ++将对i进行赋值然后对其进行递增。
Quentin Hayot 2015年

1
@QuentinHayot; 什么?评估表达后会发生任何副作用。在的情况下++i,该表达式将被求值,i将被递增,并且该递增的值将成为表达式的值。在的情况下i++,该表达式将被求值,的旧值i将是表达式的值,i将在表达式的上一个和下一个序列点之间的任何时间递增。
c

62

引自C11,章6.5.17逗号运算符

逗号运算符的左操作数被评估为void表达式;在它的评估与正确操作数的评估之间存在一个顺序点。然后评估正确的操作数;结果具有其类型和价值。

所以,就您而言

(i, ++i, 1)

被评估为

  1. i,被评估为无效表达式,值被舍弃
  2. ++i,被评估为无效表达式,值被舍弃
  3. 最终1,值返回了。

因此,最终声明看起来像

i = 1 + 1;

i到达2。我想这可以回答您的两个问题,

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

注意:FWIW,因为有一个序列中的点存在于左手操作数的评价后,表达等(i, ++i, 1)将不会调用UB,作为一个普遍认为误。


+1 Sourav,因为这解释了为什么初始化初始化i显然没有效果!但是,对于一个不知道逗号运算符的人(我不问问题,我不知道如何寻求帮助),我认为这不是那么明显。可惜我投票太多了!我将检查其他答案,然后决定接受哪个答案。谢谢!顺便说一句好答案。
gsamaras

我觉得我必须解释为什么我接受了鹰嘴豆的答案。我已经准备好接受您的问题,因为它确实回答了我两个问题。但是,如果检查我的问题的注释,您会发现有些人乍一看看不到为什么它不调用UB。吊c答案提供了一些相关信息。当然,我的问题中有关于UB的答案,但有些人可能会错过。希望您同意我的意愿,如果不让我知道。:)
gsamaras 2015年

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;

无论是在现在是覆盖前。


最好指出这是由于逗号运算符引起的。+1一步一步的分析!顺便说一句好答案。
gsamaras 2015年

很抱歉,我的解释不够充分,我在那儿只有一个字条(...但是被忽略了,有...)。我主要想解释为什么++i不有助于结果。
dlask

现在我的for循环将永远像int i = 0; for( ;(++i, i<max); )
CoffeDeveloper

19

的结果

(i, ++i, 1)

1

对于

(i,++i,1) 

进行评估时,,操作员将丢弃评估值,并仅保留最正确的值,即1

所以

i = 1 + 1 = 2

1
是的,我也想到了这一点,但我不知道为什么!
gsamaras

@gsamaras,因为逗号运算符会评估前一个术语,但会丢弃它(即不将其用于分配等)
Marco A.

14

您会在Wiki页面上找到一些有关逗号运算符的不错的阅读材料。

基本上,它

...评估其第一个操作数并丢弃结果,然后评估第二个操作数并返回此值(和类型)。

这意味着

(i, i++, 1)

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


O_O,该语法在C ++中是否有效,我记得我几乎没有需要该语法的地方(基本上我写过:(void)exp; a= exp2;而我只是需要a = exp, exp2;
CoffeDeveloper 2015年

13

您需要在这里知道逗号运算符在做什么:

您的表情:

(i, ++i, 1)

对于整个表达式,i将评估第一个表达式,对第二个表达式++i进行评估,并1返回第三个表达式。

所以结果是:i = 1 + 1

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


5

逗号具有“反”优先级。这是您从IBM(70s / 80s)的旧书和C手册中获得的。因此,最后一个“命令”是父表达式中使用的命令。

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

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

虽然从左到右调用了所有操作(函数),但只有最后一个表达式将用作条件“ while”的结果。这样可以防止对“ goto”进行处理,以使唯一的命令块在条件检查之前运行。

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


卢西亚诺,您还可以链接到此答案:stackoverflow.com/questions/17902992/…
gsamaras 2015年

在90年代初,内联函数出现之前,我经常使用它来优化代码并使代码井井有条。
Luciano 2015年
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.