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 选 General,Debug Info 选 Program Database。再在 Link 标签 Project options 最后加上 "/OPT:REF" (引号不要输)。这样调试器就能使用 pdb 文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到--这些都被优化过了。不过令人庆幸的是,Ca
ll Stack 窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。