-1

在前几天我和几个同事的一次讨论中,我拼凑了一段 C++ 代码来说明内存访问冲突。

经过很长一段时间几乎完全使用具有垃圾收集功能的语言之后,我目前正在慢慢返回 C++,我想,我失去了触摸显示,因为我对我的短程序表现出的行为感到非常困惑。

有问题的代码是这样的:

#include <iostream>

using std::cout;
using std::endl;

struct A
{
    int value;
};

void f()
{
    A* pa;    // Uninitialized pointer
    cout<< pa << endl;
    pa->value = 42;    // Writing via an uninitialized pointer
}

int main(int argc, char** argv)
{   
    f();

    cout<< "Returned to main()" << endl;
    return 0;
}

我在 Ubuntu 15.04 上使用 GCC 4.9.2 编译它并-O2设置了编译器标志。我在运行它时的期望是,当我的评论表示为“通过未初始化的指针写入”的行被执行时,它会崩溃。

然而,与我的预期相反,程序成功运行到最后,产生了以下输出:

0
Returned to main()

我用-O0标志重新编译了代码(以禁用所有优化)并再次运行程序。这一次,行为如我所料:

0
Segmentation fault

(好吧,差不多:我没想到指针会被初始化为 0。)基于这个观察,我推测当用-O2set 编译时,致命指令被优化掉了。这是有道理的,因为没有进一步的代码访问pa->value由违规行设置的之后,因此,大概编译器确定它的删除不会修改程序的可观察行为。

我多次复制了这一点,每次程序在没有优化的情况下编译时都会崩溃,而在使用-O2.

当我在's 正文pa->value的末尾添加一行输出 ,时,我的假设得到了进一步证实:f()

cout<< pa->value << endl;

正如预期的那样,有了这条线,程序总是崩溃,不管它是用什么优化级别编译的。

如果到目前为止我的假设是正确的,这一切都是有道理的。但是,如果我将代码从正文f()直接移动到 ,我的理解有些中断main(),如下所示:

int main(int argc, char** argv)
{   
    A* pa;
    cout<< pa << endl;
    pa->value = 42;
    cout<< pa->value << endl;

    return 0;
}

禁用优化后,该程序会崩溃,正如预期的那样。但是,使用-O2时,程序成功运行到最后并产生以下输出:

0
42

这对我来说毫无意义。

这个答案提到“取消引用尚未明确初始化的指针”,这正是我正在做的,作为 C++ 中未定义行为的来源之一。

main()那么,与 中的代码相比,优化影响中代码的方式的差异是否f()完全由我的程序包含 UB 的事实来解释,因此编译器在技术上可以自由地“发疯”,还是存在一些根本差异,我不知道main(),与其他例程中的代码相比,在优化代码的方式之间?

4

2 回答 2

2

您的程序具有未定义的行为。这意味着任何事情都可能发生。C++ 标准根本没有涵盖该程序。你不应该带着任何期望进去。

人们常说,不确定的行为可能会“发射导弹”或“让恶魔从你的鼻子里飞出”,以强化这一点。后者比较牵强,但前者是可行的,想象一下你的代码在一个核发射场上,而野指针恰好写了一段启动全球热核战争的内存。

于 2016-04-13T23:47:19.023 回答
1

编写未知指针一直是可能产生未知后果的事情。更糟糕的是一种当前流行的哲学,它建议编译器应该假设程序永远不会接收导致 UB 的输入,因此如果此类测试不会阻止 UB 发生,则应该优化任何测试此类输入的代码。

因此,例如,给定:

uint32_t hey(uint16_t x, uint16_t y)
{
  if (x < 60000)
    launch_missiles();
  else
    return x*y;
}
void wow(uint16_t x)
{
  return hey(x,40000);
}

32 位编译器可以合法地替换wow为无条件调用, launch_missiles而不考虑 的值x,因为x“不可能”大于 53687(超出该值的任何值都会导致 x*y 的计算溢出。即使C89 的作者指出,那个时代的大多数编译器都会在上述情况下计算出正确的结果,因为标准没有对编译器提出任何要求,超现代哲学认为它对编译器来说“更有效”假设程序永远不会收到需要依赖这些东西的输入。

于 2016-04-14T05:44:33.637 回答