您的位置:首页 > 其它

一个Debug版本不崩而Release版本可能崩的问题

2016-01-14 14:41 183 查看

引子

今天一个朋友在QQ上向我求助,说他的一个MFC程序用VS2013编译生成的Debug版本运行正常,而编译生成的Release版本却在启动后还没出现界面便崩溃了。

经过一番折腾之后,通过调试找到了崩溃点,但却根本不像是崩溃在这儿,因为崩溃在MFC的内部代码中,而MFC内部代码是几乎不可能会崩溃的。跟踪了代码之后,还是没有效果,因为崩溃处的代码怎么看都没有问题。

最后果断放弃了调试,直接去理代码了,还好只是个Dialog小程序,代码量比较少。再经过一番折腾,终于找到了问题代码。其问题代码抽象后,可以表述如下:

class A {
public:
...
bool OnInitDialog();
...
LARGE_INTEGER *frequency_;
...
};

bool A::OnInitDialog() {
...
QueryPerformanceFrequency(frequency_);
...
}


这段代码看起来问题特别小,就是对
QueryPerformanceFrequency
函数的使用错误,应该像下面这样使用这个函数:

LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency_);


思考

虽然只用纠正
QueryPerformanceFrequency
函数的用法便能解决这个问题,但这却不是我觉得这个问题的特别之处。如果再进一步挖掘这个问题的话,可以发现这个问题的特别之处在于指针问题,而导致Debug版正常而Release版不正常的原因也在于此。为什么问题代码会出现这样奇怪的现象呢,并且不容易调试出来。我觉得本质原因在于改写随机内存。

我们知道VS2013在调试版本会将内存中字节初始为
0xCC
,而当
frequency_
没有初始化时,它的值便是
0xCCCCCCCC
。如果是我们的代码访问了这个地址,调试时肯定就到这儿便崩溃了,但是
QueryPerformanceFrequency
对它的访问却没有崩溃。这便是这个问题的奇妙之处之一。如果了解一点32位Windows平台的进程地址空间,我们便知道每个进程有4G的地址空间,而这个地址空间的低2G部分加载的是用户代码,而高2G加载的便是操作系统的代码,用户代码访问进程的操作系统部分的地址空间是会触发违规访问的,从而崩溃。而
0xCCCCCCCC
这个地址是在进程的操作系统的地址空间部分,而
QueryPerformanceFrequency
这个函数是Windows的API,也就是它会转到操作系统内核,从而访问这个地址的内容没有问题,从而没有崩溃。这便解释了为什么调试版本不会崩溃。

而Release版本不会对内存作初始化,当
frequency_
没有初始化时,它的值便是随机机的。如果它所指的内存是程序中一个在用的部分,则
QueryPerformanceFrequency
便会改写它。而到后面的代码再去访问这个地址时,于是崩溃便发生了。因为这个地址的内容被必改写了,如果它以前是一个句柄,现在被乱写了,自然是会崩溃的,当然还有其它可能导致崩溃的情况。反正只要这个地址所指的内容以前是个有意义,被改写后变得没有意义了,崩溃的概率是特别大的。这便解释了为什么Release版本会崩溃,而且很难调试到,因为崩溃被转移了,这有点像借刀杀人,这便是这个问题的奇妙之处。

这也提醒我们,要绝对的提高对指针的警惕,一些看似简单的失误,导致的后期问题是你所想像不到的。另外,这个问题也告诉了我们把指针初始为
NULL
的重要性。这个问题中,如果
frequency_
被初始为
NULL
,那么对它的访问立即会导致崩溃,即便不崩溃,也不会出现改写内存的情况。

所以,通过这个问题,我们应该要不时提醒自己初始化指针为
NULL
,不然很可能会导致改写内存,而一旦出现这样的事,通过调试就算找到崩溃点,也是很难知道问题的根源出现在哪里的。而初始为
NULL
会给我们带来调试便利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: