最近在一次采访中有一个以下客观类型的问题 .
int a = 0;
cout << a++ << a;
回答:
一个 . 10
湾01
C . 未定义的行为
我回答了选择b,即输出为“01” .
但令我惊讶的是,一位采访者告诉我,正确的答案是选项c:undefined .
现在,我确实知道C中序列点的概念 . 以下语句的行为未定义:
int i = 0;
i += i++ + i++;
但根据我对语句 cout << a++ << a
的理解, ostream.operator<<()
将被调用两次,首先是 ostream.operator<<(a++)
,后来是 ostream.operator<<(a)
.
我还检查了VS2010编译器的结果,其输出也是'01' .
4 回答
你可以想到:
如:
C保证先前评估的所有副作用都将在sequence points执行 . 函数参数求值之间没有序列点,这意味着可以在参数
std::operator<<(std::cout, a++)
之前或之后评估参数a
. 所以上面的结果是不确定的 .C++17 update
在C 17中,规则已经更新 . 特别是:
这意味着它需要代码生成结果
b
,它输出01
.有关详细信息,请参阅P0145R3 Refining Expression Evaluation Order for Idiomatic C++ .
从技术上讲,总的来说这是 Undefined Behavior .
但是,答案有两个重要方面 .
代码声明:
被评估为:
该标准没有定义函数参数的评估顺序 .
所以要么:
首先评估
std::operator<<(std::cout, a++)
或首先评估
a
或它可能是任何实现定义的顺序 .
根据标准,此订单为 Unspecified [参考1] .
[参考1] C++03 5.2.2 Function call
Para 8
此外,在评估函数的参数之间没有序列点,但只有在评估了所有参数之后才存在序列点[参考文献2] .
[参考2] C++03 1.9 Program execution [intro.execution]:
Para 17:
请注意,这里
c
的值被多次访问而没有插入序列点,对此标准说:[参考3] C++03 5 Expressions [expr]:
Para 4:
代码在不插入序列点的情况下多次修改
c
,并且不会访问它以确定存储对象的值 . 这显然违反了上述条款,因此标准规定的结果是[参考文献3] .序列点仅定义部分排序 . 在你的情况下,你有(一旦完成重载决议):
a++
和第一次调用std::ostream::operator<<
之间有一个序列点,第二个a
和第二次调用std::ostream::operator<<
之间有一个序列点,但a++
和a
之间没有序列点;唯一的排序约束是a++
在第一次调用operator<<
之前被完全评估(包括副作用),并且第二次a
在第二次调用operator<<
之前被完全评估 . (还有一些因果排序约束:第二次调用operator<<
不能先写入第一个,因为它需要第一个作为参数的结果 . )§5/ 4(C 03)声明:表达式的允许排序之一是
a++
,_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _这会修改a
(a++
)的存储值,并且除了确定新值(第二个a
)之外访问它,行为是未定义的 .正确的答案是质疑这个问题 . 这种说法是不可接受的,因为读者看不清楚答案 . 另一种看待它的方法是我们引入了副作用(c),这使得语句更难以解释 . 简洁的代码很棒,提供它的意思很清楚 .