100

最近在一次采访中出现了以下客观类型的问题。

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

答案:

一种。10
湾。01
℃。未定义的行为

我回答了选项 b,即输出为“01”。

但令我惊讶的是,后来一位面试官告诉我正确答案是选项 c:未定义。

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

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

但根据我对声明的理解cout << a++ << aostream.operator<<()将被调用两次,首先是 with ostream.operator<<(a++),然后是ostream.operator<<(a)

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

4

4 回答 4

147

你可以想到:

cout << a++ << a;

作为:

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

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


C++17 更新

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

在移位运算符表达式E1<<E2andE1>>E2中, 的每个值计算和副作用E1都排在 的每个值计算和副作用之前E2

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

有关更多详细信息,请参阅P0145R3 惯用 C++ 的优化表达式评估顺序

于 2012-05-28T10:17:17.497 回答
68

从技术上讲,总的来说这是Undefined Behavior

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

代码声明:

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

被评估为:

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

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

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

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

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

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

此外,在对函数的参数求值之间没有序列点,但序列点仅在对所有参数进行求值后才存在[Ref 2]

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

调用函数时(无论函数是否内联),在对所有函数参数(如果有)求值之后都会有一个序列点,该序列点发生在函数体中的任何表达式或语句执行之前。

请注意,这里的值c被多次访问而没有中间序列点,关于这个标准说:

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

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

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

于 2012-05-28T10:16:27.537 回答
20

序列点仅定义部分排序。在您的情况下,您有(一旦完成重载解决方案):

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

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

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

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

于 2012-05-28T11:27:27.860 回答
4

正确答案是质疑问题。这种说法是不可接受的,因为读者看不到明确的答案。另一种看待它的方式是,我们引入了副作用 (c++),使语句更难解释。简洁的代码很棒,只要它的含义很清楚。

于 2012-05-29T21:06:08.217 回答