导致时间旅行的未定义行为

时间:2014-07-02 09:23:43

标签: c++ undefined-behavior

来自msdn博客的this article的一个例子让我知道了:

它说这个功能:

void unwitting(bool door_is_open)
{
 if (door_is_open) {
  walk_on_in();
 } else {
  ring_bell();

  // wait for the door to open using the fallback value
  fallback = value_or_fallback(nullptr);
  wait_for_door_to_open(fallback);
 }
}

可以优化到这个:

void unwitting(bool door_is_open)
{
    walk_on_in();
}

因为调用value_or_fallback(nullptr)是未定义的行为(本文前面已经证明了这一点)。

现在我不明白的是:运行时只有在到达该行时才进入未定义的行为。不应该发生之前/之后发生的概念,因为在运行时进入UB之前,第一段的所有可观察效果都已得到解决?

3 个答案:

答案 0 :(得分:7)

推理中有一个流程。

当编译器编写者说:我们使用Undefined Behavior来优化程序时,有两种不同的解释:

  • 大多数人都听说:我们识别未定义的行为,并决定我们可以做任何我们想做的事情(*)
  • 编译器编写器意味着:我们假设未发生未定义的行为

因此,在您的情况下:

  • 取消引用nullptr是未定义的行为
  • 因此执行value_or_fallback(nullptr)是未定义行为
  • 因此执行else分支是Undefined Behavior
  • 因此door_is_openfalse是未定义行为

由于未发生未定义的行为(程序员发誓她会遵循使用条款),door_is_open必然是true,编译器可以忽略else分支。

(*)我有点恼火,Raymond Chen实际上是这样制定的......

答案 1 :(得分:2)

确实,未定义的行为可能仅在运行时发生(例如,取消引用恰好为null的指针)。其他时候,一个程序可能静态地“形成不良,不需要诊断”(例如,如果你在模板使用之后为模板添加了明确的特化),这具有相同的效果,但是:你不能在语言你的程序将如何表现。

编译器可以使用UB积极地“优化”代码生成。在你的情况下,编译器看到第二个分支将导致UB(我假设这是静态已知的,即使你没有拼写出来),因此它可以进一步假设从未采用该分支,因为这是无法区分的:如果你做了进入第二个分支,那么行为将是未定义的,并且包括你输入第一个分支的行为。因此,编译器可以简单地将导致UB的整个代码路径视为已死并将其删除。

你无法证明出现问题。

答案 2 :(得分:0)

当人们试图将常识转化为规范然后在没有常识的情况下解释规范时会发生这种情况。在我个人看来,这是完全错误的,但这是在语言标准化过程中所做的。

在我个人看来,编译器不应该使用未定义的行为来优化代码。但目前的后现代编译器只是优化它。而标准允许两者。

您提到的特定错误行为背后的逻辑是编译器在分支上运行:如果某个分支中未定义某些内容,则它将整个分支标记为具有未定义的行为;如果某个分支有未定义的行为,它可能被任何东西取代。

关于这一切的最糟糕的事情是,新版本的编译器可能会破坏(并破坏)现有代码 - 无论是编译还是编译为无意义。而“现有代码”通常是一个非常大量的代码。