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++ << a
的ostream.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<<E2
和E1>>E2
,E1
每个值计算和副作用在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<<
; 这会修改a
( a++
)的存储值,并且除了确定新值(第二个a
)外,还会访问它,所以行为是未定义的。
正确的答案是质疑这个问题。 声明是不能接受的,因为读者看不到明确的答案。 另一种看待它的方法是我们引入了副作用(c ++),这使得陈述更加难以解释。 简洁的代码非常好,只要它的含义是明确的。