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

What is the correct answer for cout << a++ << a;?

本文关键字:lt 答案 是什么 a++ cout      更新时间:2023-10-16

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

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

答案:

一. 10
b. 01
C. 未定义的行为

我回答了选项 b,即输出将是"01"。

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

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

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

但根据我对cout << a++ << a语句的理解,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中,规则已更新。特别:

在移位运算符表达式E1<<E2E1>>E2中,E1的每个值计算和副作用都先于E2的每个值计算和副作用进行排序。

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

有关更多详细信息P0145R3请参阅优化惯用C++表达式求值顺序。

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

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

代码语句:

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 程序执行 [介绍执行]:
第17段:

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

请注意,这里c的值在没有干预序列点的情况下被多次访问,关于这一点,标准说:

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

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

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

序列点仅定义部分排序。 在您的情况下,您有(完成过载解决后):

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

a++和第一次调用之间有一个序列点 std::ostream::operator<<,并且第二次a和第二次呼叫std::ostream::operator<<,但那里a++a 之间没有序列点;唯一的订单限制因素是a++进行全面评估(包括副作用)在第一次调用operator<<之前,并且第二次a完全在第二次调用 operator<< 之前进行了评估。 (也有顺序约束:第二次调用operator<<不能在第一个之前,因为它需要第一个的结果作为参数。 §5/4 (C++03) 规定:

除非另有说明,否则计算单个运算符的操作数和子表达式个体表达,以及副作用发生的顺序,未指定。 在上一个和下一个序列点之间一个标量对象应最多修改一次其存储值表达式的计算。此外,先验值应为仅访问以确定要存储的值。 的要求对于完整表达式的子表达式;否则行为为定义。

表达式的允许排序之一是 a++a , 首先呼叫operator<<,第二次呼叫operator<<;这将修改aa++)的存储值,并访问它,而不是确定新值(第二个a),行为未定义。

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