在前几天我和几个同事的一次讨论中,我拼凑了一段 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。)基于这个观察,我推测当用-O2
set 编译时,致命指令被优化掉了。这是有道理的,因为没有进一步的代码访问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()
,与其他例程中的代码相比,在优化代码的方式之间?