对于 cout < < a + + < a; ,正确答案是什么?

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

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

答案:

答10
B 01
未定义行为

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

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

现在,我知道了 C + + 中序列点的概念:

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

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

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

12414 次浏览

Technically, overall this is Undefined Behavior.

But, there are two important aspects to the answer.

The code statement:

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

is evaluated as:

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

The standard does not define the order of evaluation of arguments to an function.
So Either:

  • std::operator<<(std::cout, a++) is evaluated first or
  • ais evaluated first or
  • it might be any implementation defined order.

This order is Unspecified[Ref 1] as per the standard.

[Ref 1]C++03 5.2.2 Function call
Para 8

The order of evaluation of arguments is unspecified. All side effects of argument expression evaluations take effect before the function is entered. The order of evaluation of the postfix expression and the argument expression list is unspecified.

Further, there is no sequence point between evaluation of arguments to a function but a sequence point exists only after evaluation of all arguments[Ref 2].

[Ref 2]C++03 1.9 Program execution [intro.execution]:
Para 17:

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body.

Note that, here the value of c is being accessed more than once without an intervening sequence point, regarding this the standard says:

[Ref 3]C++03 5 Expressions [expr]:
Para 4:

....
Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

The code modifies c more than once without intervening sequence point and it is not being accessed to determine the value of the stored object. This is clear violation of the above clause and hence the result as mandated by the standard is Undefined Behavior[参考文献3].

You can think of:

cout << a++ << a;

As:

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

C++ guarantees that all side effects of previous evaluations will have been performed at sequence points. There are no sequence points in between function arguments evaluation which means that argument a can be evaluated before argument std::operator<<(std::cout, a++) or after. So the result of the above is undefined.


C++17 update

In C++17 the rules have been updated. In particular:

In a shift operator expression E1<<E2 and E1>>E2, every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2.

Which means that it requires the code to produce result b, which outputs 01.

See P0145R3 Refining Expression Evaluation Order for Idiomatic C++ for more details.

Sequence points only define a partial ordering. In your case, you have (once overload resolution is done):

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

There is a sequence point between the a++ and the first call to std::ostream::operator<<, and there is a sequence point between the second a and the second call to std::ostream::operator<<, but there is no sequence point between a++ and a; the only ordering constraints are that a++ be fully evaluated (including side effects) before the first call to operator<<, and that the second a be fully evaluated before the second call to operator<<. (There are also causual ordering constraints: the second call to operator<< cannot preced the first, since it requires the results of the first as an argument.) §5/4 (C++03) states:

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified. Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

One of the allowable orderings of your expression is a++, a, first call to operator<<, second call to operator<<; this modifies the stored value of a (a++), and accesses it other than to determine the new value (the second a), the behavior is undefined.

The correct answer is to question the question. The statement is unacceptable because a reader cannot see a clear answer. Another way to look at it is that we have introduced side-effects (c++) that make the statement much harder to interpret. Concise code is great, providing it's meaning is clear.