首页 文章

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

提问于
浏览
97

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

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 回答

  • 4

    你可以想到:

    cout << a++ << a;
    

    如:

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

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


    C++17 update

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

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

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

    有关详细信息,请参阅P0145R3 Refining Expression Evaluation Order for Idiomatic C++ .

  • 141

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

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

    代码声明:

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

    被评估为:

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

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

    首先评估

    • 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] .

  • 68

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

    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++ ,_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _这会修改 aa++ )的存储值,并且除了确定新值(第二个 a )之外访问它,行为是未定义的 .

  • 19

    正确的答案是质疑这个问题 . 这种说法是不可接受的,因为读者看不清楚答案 . 另一种看待它的方法是我们引入了副作用(c),这使得语句更难以解释 . 简洁的代码很棒,提供它的意思很清楚 .

相关问题