VC编写程序在debug下正常,在release下错误

来源:互联网 发布:淘宝买的药是真的吗 编辑:程序博客网 时间:2024/06/06 00:29
 

参考了孟翔的日志 - 网易博客和gaohuiming的日志,本人博客旨在总结自己学习的东西,有些资料摘自网上,并无商业用途,如有问题,电邮vmespacehome@163.com

 

Debug Release 编译方式的区别

Debug Release的真正秘密,在于一组编译选项。下面列出了分别针对二者的选项(当然除此之外还有其他一些,如/Fd /Fo,但区别并不重要,通常他们也不会引起 Release版错误,在此不讨论)

Debug版本:

       /MDd /MLd /MTd  使用 Debug runtime library(调试版本的运行时刻函数库)

       /Od                 关闭优化开关

       /D "_DEBUG"         相当于 #define _DEBUG,打开编译调试代码开关(主要针对assert函数)

       /ZI                 创建 Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需重新编译

       /GZ                 可以帮助捕获内存错误

       /Gm                 打开最小化重链接开关,减少链接时间

Release版本:

        /MD /ML /MT     使用发布版本的运行时刻函数库

        /O1 /O2         优化开关,使程序最小或最快

       /D "NDEBUG"         关闭条件编译调试代码开关(即不编译assert函数)

        /GF                合并重复的字符串,并将字符串常量放到只读内存,防止被修改

       实际上,Debug Release并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。 

 

VC编写程序在debug下正常,在relea se下错误可能存在的原因:

1、内存分配问题

(1)变量初始化

        Release对程序的要求较Debug严格,应该对所有的变量(特别是指针和BOOL型)都先初始化再使用。

(2)数据溢出的问题

 如:程序段   char buffer[4];   int num;       strcpy(buffer,”abcd”);

在debug 版中buffer的NULL覆盖了num的高位,但是除非num >16M,什么问题也没有。但是在release版中, num可能被放在寄存器中,这样NULL就覆盖了buffer下面的空间,可能就是函数的返回地址,这将导致ACCESS ERROR。

(3)内存分配方式不同

        如果你在DEBUG版中申请 ele 为 6*sizeof(DWORD)=24bytes,实际上分配给你的是32bytes(debug版以32bytes为单位分配),而在release版,分配给你的就是24bytes(release版以8bytes为单位),所以在debug版中如果你写ele[6],可能不会有什么问题,而在release版中,就有ACCESS VIOLATE

2、用户自定义消息应该按照定义的方式去使用

     对于用户自定义消息: #define WM_USERMESSAGENAME WM_USER+1

     ON_MESSAGE定义: ON_MESSAGE(WM_USERMESSAGENAME, OnUserMessageName), 该宏需要两个参数,如果消息响应函数并没有参数,编译器在压栈出栈时就会出错,而DEBUG版运行时编译器会自动加一些测试代码,所以不会造成堆栈的破坏。

     解决办法:1)把ON_MESSAGE 替换成ON_MESSAGE_VOID (头文件<AFXPRIV.H>),该宏不需要参数

2)修改消息响应函数: afx_msg void OnUserMessageName(WPARAM wparam, LPARAM lparam)   //一定要加参数,不管用不用得到

3、ASSERT和VERIFY

     ASSERT在release版本中不会被编译,若在ASSERT中有空间分配等,就会带来错误。改为VERIFY即可。

4、内存分配

     保证数据创建和清除的统一性:如果一个DLL提供一个能够创建数据的函数,那么这个DLL同时应该提供一个函数销毁这些数据。数据的创建和清除应该在同一个层次上。

5、DLL的灾难

     如果你的程序使用你自己的DLL时请注意:

     1)不能将debug和release版的DLL混合在一起使用。debug都是debug版,release版都是release版。

     2)千万不要以为静态连接库会解决问题,那只会使情况更糟糕。

6、将Project Settings中 C++/C 项目下优化选项改为 Disable(Debug)

     编译器的优化可能导致许多意想不到的错误。

     参考:http://www.cygnus-software.com/papers/release_debugging.html

另附zz:

(1) 帧指针(Frame Pointer)省略(简称 FPO ):在函数调用过程中,所有调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不同(参数、返回值、调用方式),就会产生错误 ————但 Debug 方式下,栈的访问通过 EBP 寄存器保存的地址实现,如果没有发生数组越界之类的错误(或是越界“不多”),函数通常能正常执行;Release 方式下,优化会省略 EBP栈基址指针,这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误,但如果用了强制类型转换,就不行了。你可以在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略,以确定是否此类错误。

(2) volatile 型变量:volatile 告诉编译器该变量可能被程序之外的未知方式修改(如系统、其他进程和线程)。优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于 register 关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。如果你的程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确 的设置了,则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。

怎样"调试" Release版的程序

   遇到 Debug成功但 Release失败,显然是一件很沮丧的事,而且往往无从下手。如果你看了以上的分析,结合错误的具体表现,很快找出了错误,固然很好。但如果一时找不出,以下给出了一些在这种情况下的策略。

   1. 前面已经提过,Debug Release只是一组编译选项的差别,实际上并没有什么定义能区分二者。我们可以修改 Release版的编译选项来缩小错误范围。如上所述,可以 Release的选项逐个改为与之相对的 Debug选项,如 /MD改为 /MDd/O1改为 /Od,或运行时间优化改为程序大小优化。注意,一次只改一个选项,看改哪个选项时错误消失,再对应该选项相关的错误,针对性地查找。这些选项在Project/Settings...中都可以直接通过列表选取,通常不要手动修改。由于以上的分析已相当全面,这个方法是最有效的。

   2. 在编程过程中就要时常注意测试 Release版本,以免最后代码太多,时间又很紧

   3. Debug版中使用 /W4警告级别,这样可以从编译器获得最大限度的错误信息,比如 if( i =0 )就会引起 /W4 警告。不要忽略这些警告,通常这是你程序中的 Bug引起的。但有时 /W4会带来很多冗余信息,如未使用的函数参数警告,而很多消息处理函数都会忽略某些参数。我们可以用

     #progma warning(disable: 4702) //禁止

     //...

     #progma warning(default: 4702) //重新允许

来暂时禁止某个警告,或使用

     #progma warning(push, 3) //设置警告级别为 /W3

     //...

     #progma warning(pop) //重设为 /W4

来暂时改变警告级别,有时你可以只在认为可疑的那一部分代码使用 /W4

   4.你也可以像 Debug一样调试你的 Release版,只要加入调试符号。在 Project/Settings...中,选中 Settings for "Win32 Release",选中 C/C++ 标签,Category GeneralDebug Info Program Database。再在 Link标签 Project options 最后加上 "/OPT:REF" (引号不要输)。这样调试器就能使用 pdb 文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到--这些都被优化过了。不过令人庆幸的是,Ca

ll Stack窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。