cout << a ++ << a ;?的正确答案是什么?

pra*_*avs 98 c++ c++-faq

最近在一次采访中有一个以下客观类型的问题.

int a = 0;
cout << a++ << a;
Run Code Online (Sandbox Code Playgroud)

回答:

一个.10
b.01
c.未定义的行为

我回答了选择b,即输出为"01".

但令我惊讶的是,一位采访者告诉我,正确的答案是选项c:undefined.

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

int i = 0;
i += i++ + i++;
Run Code Online (Sandbox Code Playgroud)

但根据我对该陈述的理解cout << a++ << a,ostream.operator<<()将被召唤两次,先是ostream.operator<<(a++)后来ostream.operator<<(a).

我还检查了VS2010编译器的结果,其输出也是'01'.

Max*_*kin 144

你可以想到:

cout << a++ << a;
Run Code Online (Sandbox Code Playgroud)

如:

std::operator<<(std::operator<<(std::cout, a++), a);
Run Code Online (Sandbox Code Playgroud)

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


C++ 17更新

在C++ 17中,规则已经更新.特别是:

在移位运算符表达E1<<E2E1>>E2,每一个值计算和的副作用E1的每一个值的计算和副作用之前测序E2.

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

有关详细信息,请参阅P0145R3精炼用于Idiomatic C++的表达式评估顺序.

  • C++标准中不再使用"序列点".它是不精确的,并且已经被'之前排序/排序后'关系所取代. (11认同)
  • @pravs:`operator <<`是成员函数还是独立函数不影响序列点. (2认同)
  • `所以上面的结果是未定义的.你的解释只适用于**未指定的**,不适用于**undefined**.JamesKanze解释了如何更糟糕的**未定义**[虽然他的回答](http://stackoverflow.com/a/10783921). (2认同)

Alo*_*ave 68

从技术上讲,总体而言,这是未定义的行为.

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

代码声明:

std::cout << a++ << a;
Run Code Online (Sandbox Code Playgroud)

被评估为:

std::operator<<(std::operator<<(std::cout, a++), a);
Run Code Online (Sandbox Code Playgroud)

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

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

根据标准,此订单未指定[参考1].

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

参数的评估顺序未指定.参数表达式求值的所有副作用在输入函数之前生效.未指定后缀表达式和参数表达式列表的评估顺序.

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

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

在调用函数时(无论函数是否为内联函数),在评估函数体中任何表达式或语句之前发生的所有函数参数(如果有)之后,都会有一个序列点.

请注意,这里的值c不止一次被访问而没有插入序列点,对此标准说:

[参考3] C++ 03 5表达式[expr]:
第4段:

....
在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改一次其存储值.此外,只能访问先前值以确定要存储的值.对于完整表达式的子表达式的每个允许排序,应满足本段的要求; 否则行为未定义.

代码c不会在没有插入序列点的情况下修改多次,并且不会访问它来确定存储对象的值.这明显违反了上述条款,因此标准规定的结果是未定义行为[参考文献3].

  • 新的C++ 0x标准基本上相同,但在不同的部分和不同的措辞:) Quote:(1.9程序执行[intro.execution],标准15):*"如果对标量对象的副作用是无序的相对对于相同标量对象的另一个副作用或使用相同标量对象的值的值计算,行为是未定义的."* (4认同)
  • 我相信这个答案有一个错误。"std::cout&lt;&lt;c++&lt;&lt;c;" 不能转换为“std::operator&lt;&lt;(std::operator&lt;&lt;(std::cout, c++), c)”,因为 std::operator&lt;&lt;(std::ostream&amp;, int) 不存在。相反,它转换为“std::cout.operator&lt;&lt;(c++).operator(c);”,它实际上在“c++”和“c”的计算之间有一个序列点(重载运算符被认为是函数调用,因此函数调用返回时有一个序列点)。因此行为和执行顺序*是*指定的。 (2认同)

Jam*_*nze 19

序列点仅定义部分排序.在你的情况下,你有(一旦完成重载决议):

std::cout.operator<<( a++ ).operator<<( a );
Run Code Online (Sandbox Code Playgroud)

a++在第一次调用和第一次调用 之间std::ostream::operator<<有一个序列点,在第二次a和第二次调用之间有一个序列点std::ostream::operator<<,但是a++和之间没有序列点a; 唯一的排序约束是a++在第一次调用之前完全评估(包括副作用)operator<<,第二次a在第二次调用之前完全评估operator<<.(也存在因果排序约束:第二次调用operator<<不能首先进行,因为它需要第一次作为参数的结果.)§5/ 4(C++ 03)声明:

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

你的一个表达的允许排序的是a++,a首先调用operator<<,第二次调用operator<<; 这会修改a(a++)的存储值,并访问它而不是确定新值(第二个a),行为是未定义的.