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

引子

  今天一个朋友在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会给我们带来调试便利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值