【问题标题】:Unexpected order of evaluation (compiler bug?) [duplicate]意外的评估顺序(编译器错误?)[重复]
【发布时间】:2011-07-10 00:48:44
【问题描述】:

可能重复:
Undefined Behavior and Sequence Points

我不确定这是否是 gcc 错误,所以我会问:

unsigned int n = 0;
std::cout << n++ << n << ++n;

gcc 给出了极其奇怪的结果: AFAICT不可能的“122”。因为

operator<<(operator<<(operator<<(std::cout, n++), n), ++n)

并且因为在评估参数之前和之后都有一个序列点,所以 n 在两个序列点之间永远不会被修改两次(甚至被访问)——所以它不应该是未定义的行为,只是未指定的评估顺序。

所以 AFAICT 的有效结果是: 111 012 002 101

没有别的

【问题讨论】:

  • 不是编译器错误;与您的预期相反,这是 C++ 中未定义的行为。
  • @ephemient: 未指定,不是吗?
  • @Oli:ephemient 是正确的,它是未定义的行为。 1.9p15(在我的回答中引用)

标签: c++ gcc compiler-construction operator-precedence


【解决方案1】:

在计算参数和调用函数之间有一个序列点。评估不同参数之间没有顺序点。

我们看一下最外层的函数调用:

operator<<(operator<<(operator<<(std::cout, n++), n), ++n)

参数是

  • operator&lt;&lt;(operator&lt;&lt;(std::cout, n++), n)

  • ++n

未指定首先评估其中的哪一个。还允许在计算第二个参数时对第一个参数进行部分计算。

来自标准,[intro.execution] 部分(措辞来自草案 3225):

  • 如果 A 之前没有排序 BB不排在A之前,那么AB是未排序。 [ 注意: unsequenced 的执行 评价可以重叠。 — 尾注 ]

  • 除非另有说明,否则单个运算符的操作数和单个运算符的子表达式的求值 表达式是无序的。 [ 注意: 在执行期间多次计算的表达式中 对于程序,其子表达式的未排序和不确定排序的评估不需要 在不同的评价中表现一致。 — end note ] 一个操作数的值计算 运算符在运算符结果的值计算之前排序。如果对标量产生副作用 相对于同一标量对象的另一个副作用或值计算,对象是无序的 使用相同标量对象的值,行为未定义。

因为您在同一个标​​量对象上进行了多个具有副作用的操作,这些操作彼此之间没有顺序,所以您处于未定义行为的领域,甚至999 也是允许的输出。

【讨论】:

  • 谢谢本,这似乎是答案!我的印象是在评估不同的论点之间有一个顺序点,但似乎没有。也感谢你不居高临下的态度:)
  • @user: 不...逗号运算符对其操作数进行排序,但函数参数之间使用的逗号不是逗号运算符。
  • 我原以为包含函数调用的表达式中的操作相对于该调用是不确定的,而不是无序的。当然,如果一个表达式包含两个函数调用,它们相对于彼此的顺序是不确定的,而不是无序的。
  • @supercat:它们在函数调用中的顺序是不确定的,但彼此之间没有顺序。
  • @BenVoigt:我明白了。您声明在评估第二个参数时可能会部分评估第一个参数,但我认为您的真正意思是当 arguments 到内部函数调用时可能会部分评估第一个参数被评估。函数调用本身——参数评估的关键部分——需要完全在第一次递增操作之前或之后发生,不是吗?
【解决方案2】:

编译器错误的第一条规则:它可能不是编译器错误,而是您的误解。在同一语句中使用后缀和前缀运算符会导致未定义的行为。尝试使用 -Wall 选项给您更多警告并向您展示代码中的潜在缺陷。

让我们看看 GCC 4.2.1 在我们询问有关 test.cpp 的警告时会告诉我们什么:

#include <iostream>

int main() {
    unsigned int n = 0;
    std::cout << n++ << n << ++n << std::endl;
    return 0;
}

当我们编译时:

$ g++ -Wall test.cpp -o test
test.cpp: In function ‘int main()’:
test.cpp:5: warning: operation on ‘n’ may be undefined

【讨论】:

  • 该规则有一些例外,例如当一个人使用来自某家主要日本电子集团的工具时。 =P
  • 在这种情况下,它不是 undefined 行为,而只是 unspecified。这与标准的i = i + ++i 类型问题不同...
  • @Oli:在相同的编译器和平台下,“未指定”是否可以重现,而“未定义”可能会从一个程序运行到下一个程序运行时发生变化?无论语义上的差异如何,GCC 都将其报告为“未定义”,尽管我同意恶魔并没有从我的鼻子里飞出来。
  • 我同意,在实践中,两者都不是值得依赖的好东西。不过,标准好像就好像做个歌舞一下的区别!不过,我不确定为什么。
  • @Oli:根据草案 n3225,它 未定义的行为
【解决方案3】:

你的代码是一个例子,说明为什么在一些书中有经验的程序员不喜欢 that(++,--) 运算符重载,甚至其他语言(ruby)也没有实现 ++或 --。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-19
    • 2012-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多