cout << a ++ << a;是什么答案?

最近在一次采访中,有一个客观types的问题。

int a = 0; cout << a++ << a; 

回答:

一个。 10
湾 01
C。 未定义的行为

我回答了selectb,即输出将是“01”。

但是让我吃惊的是后来,一位面试官告诉我,正确的答案是选项c:undefined。

现在,我知道C ++中序列点的概念。 以下语句的行为未定义:

 int i = 0; i += i++ + i++; 

但根据我对语句cout << a++ << aostream.operator<<()ostream.operator<<()将被调用两次,第一次使用ostream.operator<<(a++)和后来的ostream.operator<<(a)

我也检查了VS2010编译器的结果,它的输出也是'01'。

你可以想到:

 cout << a++ << a; 

如:

 std::operator<<(std::operator<<(std::cout, a++), a); 

C ++保证以前评估的所有副作用将在序列点执行。 在函数参数评估之间没有序列点,这意味着参数a可以在参数std::operator<<(std::cout, a++)或之后求值。 所以上面的结果是不确定的。


C ++ 17更新

在C ++ 17中,规则已被更新。 尤其是:

在移位算子expression式E1<<E2E1>>E2E1每个值计算和副作用在E2每个值计算和副作用之前被sorting。

这意味着它需要代码产生结果b ,输出01

有关更多详细信息,请参阅P0145R3精炼expression式评估顺序以了解更多信息。

从技术上讲,总的来说这是未定义的行为

但是,答案有两个重要方面。

代码声明:

 std::cout << a++ << a; 

被评估为:

 std::operator<<(std::operator<<(std::cout, a++), a); 

该标准没有定义函数参数的评估顺序。
所以:

  • std::operator<<(std::cout, a++)首先被评估
  • a首先被评估或
  • 它可能是任何实现定义的顺序。

这个订单是未指定的 [Ref 1]根据标准。

[参考1] C ++ 03 5.2.2函数调用
第8段

参数的评估顺序是未指定的 。 参数expression式评估的所有副作用在input函数之前生效。 未指定后缀expression式和参数expression式列表的评估顺序。

此外,在函数的参数评估之间没有序列点,但是只有在评估所有参数后才存在序列点[参考文献2]

[参考2] C ++ 03 1.9程序执行[intro.execution]:
第17段:

当调用一个函数(函数是否是内联函数)时,在执行函数体中的任何expression式或语句之前,所有函数参数(如果有的话)的求值之后都有一个序列点。

注意,这里c的值正在被访问不止一次,没有一个中间的顺序点,对此标准说:

[参考3] C ++ 03 5expression式[expr]:
段落4:

….
在前一个和下一个序列点之间,一个标量对象应该通过评估一个expression式来最多修改其存储值。 此外,只有在确定要存储的值时才能访问先前值 。 对于一个完整expression式的子expression式的每个可允许的sorting,应满足本段的要求; 否则行为是不确定的

该代码不止一次修改c而不干涉顺序点,并且不被访问来确定存储的对象的值。 这明显违反了上述条款,因此标准规定的结果是未定义行为 [参考文献3]

序列点只定义了部分sorting。 在你的情况下,你有(一旦重载parsing完成):

 std::cout.operator<<( a++ ).operator<<( a ); 

a++和第一次调用std::ostream::operator<<之间有一个序列点,第二次调用和第二次调用std::ostream::operator<<之间有一个序列点,但是在那里在a++a之间没有序列点; 唯一的sorting约束是在第一次调用operator<<之前a++被充分评估(包括副作用),第二a在调用operator<<之前被完全评估。 (也有因果顺序约束:第二次调用operator<<不能先于第一个,因为它需要第一个结果作为参数。)§5/ 4(C ++ 03)指出:

除非另有说明,否则个体运算符的操作数和个别expression式的子expression式的评估顺序以及副作用发生的顺序是未指定的。 在前一个和下一个序列点之间,一个标量对象应该通过评估一个expression式来最多修改其存储值。 此外,只有在确定要存储的值时才能访问先前值。 对于一个完整expression式的子expression式的每个可允许的sorting,应满足本段的要求; 否则行为是不确定的。

您的expression式的允许sorting之一是a++ ,首先调用operator<< ,第二次调用operator<< ; 这会修改aa++ )的存储值,并且除了确定新值(第二个a )外,还会访问它,所以行为是未定义的。

正确的答案是质疑这个问题。 声明是不能接受的,因为读者看不到明确的答案。 另一种看待它的方法是我们引入了副作用(c ++),这使得陈述更加难以解释。 简洁的代码非常好,只要它的含义是明确的。