Debug 运行正常,Release版本不能正常运行总结(转)

引言
     若是在您的开发过程当中遇到了常见的错误,或许您的Release版本不能正常运行而Debug版本运行无误,那么我推荐您阅读本文:由于并不是如您想象的那样,Release版本能够保证您的应用程序能够象Debug版本同样运行。
若是您在开发阶段完成以后或者在开发进行一段时间以内历来没有进行过Release版本测试,然而当您测试的时候却发现问题,那么请看咱们的调试规则1:
规则1:
  常常性对开发软件进行Debug和Release版本的常规测试. 测试Release版本的时间间隔越长,排除问题的难度越大,至少对Release版本进行每周1次的测试,可使您在紧凑的开发周期内节省潜在的排故时间. 不要随意删除Release版本须要的代码这点看起来彷佛再明显不过,但倒是开发人员无心中常常犯的错误,缘由在于编译器编译Release版本时候会主动排除在代码中存在的宏,例如ASSERT和TRACE在Release版本会自动排除,这样致使的问题是您在这些宏当中运行的代码也被随之删除,这是很是危险的事情J,例如: ASSERT(m_ImageList.Create(MAKEINTRESOURCE(IDB_IMAGES), 16, 1, RGB(255,255,255))); 这样的代码在Debug模式不会出错,图像列表也自动建立了,然而在Release版本呢?后继使用m_ImageList对象只会形成程序的Crash!,所以ASSERT宏中尽可能使用逻辑运算符做为验证。
规则 2:
   不要将代码放置在仅在某种编译选项中执行的地方,对于使用_DEBUG等编译选项宏内部的代码必须不影响整个程序的使用。
规则 3:
  不要使用规则2做为评判标准来删除ASSERT宏,ASSERT宏是个有用的工具,但容易使用错误. 使Debug编译模式接近Release模式若是您的Release版本存在的问题是由代码被编译器自动排除形成的,那么经过这个方法您的问题可能会重现. 一些问题的产生多是因为不一样编译选项之间预约义符号形成的,所以您能够更改编译模式下的预约义符号,从而使您的Debug模式接近Release模式,观察错误是否产生,更改编译预约义符号方法以下: a.. Alt-F7打开项目设置,在C++/C 页面,选择"General"类别,更改"_DEBUG"符号为"NDEBUG". b.. 在C++/C 页面, 选择"Preprocessor"类别,添加预约义符号"_DEBUG"到"Undefined Symbols"栏. c.. 使用"Rebuild All"从新编译若是经过上面设置,您在Release编译模式下面的问题在Debug模式下重现,那么请您依据如下步骤对您的代码进行修改: a.. 查找ASSERT排除其中的全部重要执行语句,或者将ASSERT修改成VERIFY. b.. 检查"#ifdef _DEBUG" 内全部代码,排除Release模式使用的代码. c.. 查找TRACE 排除其中的全部重要执行语句. TRACE和ASSERT同样,仅在Debug模式下编译. 若是经过上面修改更正了您在Debug模式下的问题,那么您能够从新编译Release模式,很是有可能您能够解决先前存在的问题!. 错误的假定形成编译模式错误您是否常常性的假定您的变量或者对象被初试化成某个指定的值(可能0)?您是否假定你全部关联到的资源在应用程序中都存在?这些也是Debug和Release模式下不一样问题产生的缘由。
规则 4:
  除非您在代码中对变量进行初始化,不然不能做出如上假定. 包括全局变量,自动变量,申请对象和new对象. 这种状况还经常发生在内存顺序的问题,记得原来使用结构体的时候为了使用方便,比较两个结构体对象使用memcmp,在Debug版本工做正常,而Release版本计算出错误的解,看来的确不能进行错误的假定!
规则 5:
  确保删除资源的全部引用都被删除,例如resource.h中的定义. 软件开发中,不一样编译版本对变量和内存的初始化是不一样的. 若是您假定变量初始化为0,那么在Win9x系统的Release模式下,会出现异常现象。所以对全部变量,内存显式清0是较为安全的作法. 若是您引用了已经被删除的资源,您的Debug版本能够正常工做,可是Release版本可能会crash. 您是否相信编译器? 编译器警告级别和编译噪音有着至关大的关系. 经过提升编译器警告级别可增长程序隐藏问题暴露的机会.一般设置警告级别在"Level 3"或者 "Level 4".编译并解决全部警告,这是发布Release版本应用程序的一个很好的建议.这能暴露会使您的应用程序出现问题的不少初始化问题和其它潜在的错误.
规则 6:
  开始项目以前先将编译警告级别设置在"Level 3" 或者 "Level 4" ,登记代码以前确保消灭全部警告!. 总结报告编译模式下的调试曾经不止一次的听到一些VC开发者说Release模式下面不能进行调试,幸运的是:经过相应设置,能够在Release模式进行调试,所以那只不过是一个以讹传讹的荒谬说法而已。
 规则 7:
  当前面全部的方法都无效的时候,在Release模式下面进行调试. Release模式能够进行调试,第一步是打开符号表: a.. Alt-F7打开项目设置,在C++/C 页面,选择"General"类,修改Debug Info setting 为 "Program Database". b.. 在"Link" 页面,选择"Generate Debug Info". c.. "Rebuild All" 这些设置将容许您在Release模式下保留符号表,您也能够同时考虑如下设置: a.. 调试Release版本应用程序,您能够关闭优化选项. b.. 若是在Release模式下面不能设置断点,添加指令"__asm {int 3}" 可使您的应用程序在该行中止(肯定在发布应用程序时候排除这些代码). 在Release模式进行调试的几个限制. a.. 最大的问题在于您不能跟踪到MFC函数内部,缘由在于Release版本的MFC动态连接库不包含调试信息和符号表. b.. 同上,想要调试调用的dll,您必须给它们所有加上调试信息和符号表. 编译器生成了错误的代码? 或许有的时候您会发现VC++编译器生成了’问题代码’,然而坦率的讲,人们一般抱怨的太早.您能够在Release模式下面关闭优化选项来进行测试. 若是这个操做解决了您的问题,或许您的编码习惯存在问题. 信不信由你, 极其可能在您的编码中存在模棱两可的求解或者看起来彷佛正确,某些条件下也是正确的状况. 举个例子,下面的代码在Debug模式彷佛一切’正常’,而在Release模式下面却会出错! #include int* func1(){int retval = 5;return &retval;} int main(int argc, char* argv[]){printf("%d/n", *func1());return 0;}我相信大多数程序员尤为是初学者容易遇到此类状况的。
规则 8:
  若是关闭Release模式的优化选项可使您的应用程序运行正常,而打开优化选项则出现问题的化,缘由多半在于您的不良编码习惯形成的. 这意味着必须仔细检查您的代码,清理出那些错误的假设,悬空指针等等. 等同的这告诉您,在Debug模式和关闭优化选项的Release模式下您的应用程序工做正常全是由于系统隐含的运气,您必须着手更正存在隐患的代码,不然在往后可能会形成巨大的损失。
 规则 9:
  若是您已经完全检查了您的代码,而且没有发现问题,那么您最好逐个打开优化选项将产生错误的缘由限制在某个范围以内. BTW- 以上问题代码由C++编译器自动检出. 若是您已经遵循 规则 6 您或许在前面环节中已经解决了这些问题. 凭个人开发经验,编译器极少会产生错误的代码(固然要注意接口程序边界对齐的问题).一般在使用模板类时候VC6编译器或许会产生断言ASSERT错误,这种状况您只需更新补丁便可解决。
      最后的思考在平常编码中只需稍微增长一点严格的检测,便能有效的避免新的Debug -v- Release模式问题的产生,如下是个人一些经验。
     1. 取出(check out)须要修改的代码。
     2. 修改代码,排除全部警告,编译Debug和Release版本.
     3. 详细测试新代码,即单步调试新代码段以后进入工做代码,确保代码无误.
     4. 更正全部问题.
     5. 确认无误以后将新代码登记入库(check in).
     6. 对登记入库的代码进行全新的编译,确保新登记代码与其它代码融合.
     7. 从新详细测试代码.
     8. 更正新问题(或许能够发现登记入库代码存在的问题) 严格按照以上步骤,您在设计开发过程当中便可解决大量问题,避免在最后发布应用程序时候产生新的难以定位的问题.
  后记本文是在个人开发历程中遇到Release版本应用程序发布,产生错误的时候苦苦求索获得的一些经验,原文来自于codeproject,通过本人润色,改写成为适合国内开发者的文章,但愿能对你们有用,谢谢! 转载: Debug版本包括调试信息,因此要比Release版本大不少(可能大数百K至数M)。至因而否须要DLL支持,主要看你采用的编译选项。若是是基于ATL的,则Debug和Release版本对DLL的要求差很少。若是采用的编译选项为使用MFC动态库,则须要MFC42D.DLL等库支持,而Release版本须要MFC42.DLL支持。Release Build不对源代码进行调试,不考虑MFC的诊断宏,使用的是MFC Release库,编译十对应用程序的速度进行优化,而Debug Build则正好相反,它容许对源代码进行调试,能够定义和使用MFC的诊断宏,采用MFC Debug库,对速度没有优化。
  • 1、Debug 和 Release 编译方式的本质区别 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 并无本质的界限,他们只是一组编译选项的集合,编译器只是按照预约的选项行动。事实上,咱们甚至能够修改这些选项,从而获得优化过的调试版本或是带跟踪语句的发布版本。
  • 2、哪些状况下 Release 版会出错 有了上面的介绍,咱们再来逐个对照这些选项看看 Release 版错误是怎样产生的 1. Runtime Library:连接哪一种运行时刻函数库一般只对程序的性能产生影响。调试版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,所以性能不如发布版本。编译器提供的 Runtime Library 一般很稳定,不会形成 Release 版错误;却是因为 Debug 的 Runtime Library 增强了对错误的检测,如堆内存分配,有时会出现 Debug 有错但 Release 正常的现象。应当指出的是,若是 Debug 有错,即便 Release 正常,程序确定是有 Bug 的,只不过多是 Release 版的某次运行没有表现出来而已。 2. 优化:这是形成错误的主要缘由,由于关闭优化时源程序基本上是直接翻译的,而打开优化后编译器会做出一系列假设。这类错误主要有如下几种:
    •  (1) 帧指针(Frame Pointer)省略(简称 FPO ):在函数调用过程当中,全部调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不一样(参数、返回值、调用方式),就会产生错误――――但 Debug 方式下,栈的访问经过 EBP 寄存器保存的地址实现,若是没有发生数组越界之类的错误(或是越界“很少”),函数一般能正常执行;Release 方式下,优化会省略 EBP 栈基址指针,这样经过一个全局指针访问栈就会形成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误,但若是用了强制类型转换,就不行了。你能够在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略,以肯定是否此类错误。此类错误一般有: ● MFC 消息响应函数书写错误。正确的应为 afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); ON_MESSAGE 宏包含强制类型转换。防止这种错误的方法之一是重定义 ON_MESSAGE 宏,把下列代码加到 stdafx.h 中(在#include "afxwin.h"以后),函数原形错误时编译会报错#undef ON_MESSAGE #define ON_MESSAGE(message, memberFxn) / { message, 0, 0, 0, AfxSig_lwl, / (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL / CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },
    • (2) volatile 型变量:volatile 告诉编译器该变量可能被程序以外的未知方式修改(如系统、其余进程和线程)。优化程序为了使程序性能提升,常把一些变量放在寄存器中(相似于 register 关键字),而其余进程只能对该变量所在的内存进行修改,而寄存器中的值没变。若是你的程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则极可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。
    • (3) 变量优化:优化程序会根据变量的使用状况优化变量。例如,函数中有一个未被使用的变量,在 Debug 版中它有可能掩盖一个数组越界,而在 Release 版中,这个变量极可能被优化调,此时数组越界会破坏栈中有用的数据。固然,实际的状况会比这复杂得多。与此有关的错误有: ● 非法访问,包括数组越界、指针错误等。例如 void fn(void) { int i; i = 1; int a[4]; { int j; j = 1; } a[-1] = 1;//固然错误不会这么明显,例以下标是变量 a[4] = 1; } j 虽然在数组越界时已出了做用域,但其空间并未收回,于是 i 和 j 就会掩盖越界。而 Release 版因为 i、j 并未其很大做用可能会被优化掉,从而使栈被破坏。 3. _DEBUG 与 NDEBUG :当定义了 _DEBUG 时,assert() 函数会被编译,而 NDEBUG 时不被编译。除此以外,VC++中还有一系列断言宏。这包括: ANSI C 断言 void assert(int expression ); C Runtime Lib 断言 _ASSERT( booleanExpression ); _ASSERTE( booleanExpression ); MFC 断言 ASSERT( booleanExpression ); VERIFY( booleanExpression ); ASSERT_VALID( pObject ); ASSERT_KINDOF( classname, pobject ); ATL 断言 ATLASSERT( booleanExpression ); 此外,TRACE() 宏的编译也受 _DEBUG 控制。 全部这些断言都只在 Debug版中才被编译,而在 Release 版中被忽略。惟一的例外是 VERIFY() 。事实上,这些宏都是调用了 assert() 函数,只不过附加了一些与库有关的调试代码。若是你在这些宏中加入了任何程序代码,而不仅是布尔表达式(例如赋值、能改变变量值的函数调用 等),那么 Release 版都不会执行这些操做,从而形成错误。初学者很容易犯这类错误,查找的方法也很简单,由于这些宏都已在上面列出,只要利用 VC++ 的 Find in Files 功能在工程全部文件中找到用这些宏的地方再一一检查便可。另外,有些高手可能还会加入 #ifdef _DEBUG 之类的条件编译,也要注意一下。 顺便值得一提的是 VERIFY() 宏,这个宏容许你将程序代码放在布尔表达式里。这个宏一般用来检查 Windows API 的返回值。有些人可能为这个缘由而滥用 VERIFY() ,事实上这是危险的,由于 VERIFY() 违反了断言的思想,不能使程序代码和调试代码彻底分离,最终可能会带来不少麻烦。所以,专家们建议尽可能少用这个宏。 4. /GZ 选项:这个选项会作如下这些事 (1) 初始化内存和变量。包括用 0xCC 初始化全部自动变量,0xCD ( Cleared Data ) 初始化堆中分配的内存(即动态分配的内存,例如 new ),0xDD ( Dead Data ) 填充已被释放的堆内存(例如 delete ),0xFD( deFencde Data ) 初始化受保护的内存(debug 版在动态分配内存的先后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样作的好处是这些值都很大,做为指针是不可能的(并且 32 位系统中指针不多是奇数值,在有些系统中奇数的指针会产生运行时错误),做为数值也不多遇到,并且这些值也很容易辨认,所以这颇有利于在 Debug 版中发现 Release 版才会遇到的错误。要特别注意的是,不少人认为编译器会用 0 来初始化变量,这是错误的(并且这样很不利于查找错误)。 (2) 经过函数指针调用函数时,会经过检查栈指针验证函数调用的匹配性。(防止原形不匹配) (3) 函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一块儿可大体模拟帧指针省略 FPO ) 一般 /GZ 选项会形成 Debug 版出错而 Release 版正常的现象,由于 Release 版中未初始化的变量是随机的,这有可能使指针指向一个有效地址而掩盖了非法访问。 除此以外,/Gm /GF 等选项形成错误的状况比较少,并且他们的效果显而易见,比较容易发现。 在使用VC开发软件的过程当中,正当要享受那种兴奋的时候忽然发现: release与debug运行结果不一致,甚至出错,而release又不方便调试,真的是当头一棒啊,但是疼归疼,问题总要解决,下面将讲述一下个人两点经验,看看是否是其中之一: 1. 变量。你们都知道,debug跟release在初始化变量时所作的操做是不一样的,debug是将每一个字节位都赋成0xcc,而release的赋值近似于随机(我想是直接从内存中分配的,没有初始化过,但debug为何不是0xff或0x00或其余什么的就不得而知了)。这样就明确了,若是你的程序中的某个变量没被初始化就被引用,就颇有可能出现异常:用做控制变量将致使流程导向不一致;用做数组下标将会使程序崩溃;更加多是形成其余变量的不许确而引发其余的错误。因此在声明变量后立刻对其初始化一个默认的值是最简单有效的办法,不然项目大了你找都没地方找。代码存在错误在debug方式下可能会忽略而不被察觉到,如debug方式下数组越界也大多不会出错,在release中就暴露出来了,这个找起来就比较难了:( 仍是本身多加注意吧 2. 自定义消息的消息参数。 MFC为咱们提供了很好的消息机制,更增长了自定义消息,好处我就不用多说了。这也存在debug跟release的问题吗?答案是确定的。在自定义消息的函数体声明时,时常会看到这样的写法:afx_msg LRESULT OnMessageOwn(); 当你在多线程或进程间使用了消息传递时就会致使无效句柄之类的错误。这个缘由就是消息体的参数没有添加,即应该写成:afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); (具体缘由我也不太清楚,是否是由于调用时按着默认的参数多分配了WPARAM+LPARAM的空间而破坏了应用程序的内存空间?还请高手多指点) 避免的方法: 1. 注意变量的初始化 2. 自定义消息的标准写法 3. 使用调试语句TRACE等时使用后最好注释掉 4. 尽可能使用try - catch(...) VERIFY 和 ASSERT 的区别:一个在Release下面能够执行,一个不能够 DEBUG和RELEASE 版本差别及调试相关问题: 内存分配问题 1. 变量未初始化。下面的程序在debug中运行的很好。 thing * search(thing * something) BOOL found; for(int i = 0; i < whatever.GetSize(); i++) { if(whatever[i]->field == something->field) { /* found it */ found = TRUE; break; } /* found it */ } if(found) return whatever[i]; else return NULL; 而在release中却不行,由于debug中会自动给变量初始化found=FALSE,而在release版中则不会。因此尽量的给变量、类或结构初始化。 2. 数据溢出的问题 如:char buffer[10]; int counter; lstrcpy(buffer, "abcdefghik"); 在debug版中buffer的NULL覆盖了counter的高位,可是除非counter>16M,什么问题也没有。可是在release版中,counter可能被放在寄存器中,这样NULL就覆盖了buffer下面的空间,可能就是函数的返回地址,这将致使ACCESS ERROR。 3. DEBUG版和RELEASE版的内存分配方式是不一样的 。若是你在DEBUG版中申请 ele 为 6*sizeof(DWORD)=24bytes,实际上分配给你的是32bytes(debug版以32bytes为单位分配), 而在release版,分配给你的就是24bytes(release版以8bytes为单位),因此在debug版中若是你写ele[6],可能不会有什么问题,而在release版中,就有ACCESS VIOLATE。 II. ASSERT和VERIFY 1. ASSERT在Release版本中是不会被编译的。 ASSERT宏是这样定义的 #ifdef _DEBUG #define ASSERT(x) if( (x) == 0) report_assert_failure() #else #define ASSERT(x) #endif 实际上复杂一些,但可有可无。假如你在这些语句中加了程序中必需要有的代码 好比 ASSERT(pNewObj = new CMyClass); pNewObj->MyFunction(); 这种时候Release版本中的pNewObj不会分配到空间 因此执行到下一个语句的时候程序会报该程序执行了非法操做的错误。这时能够用VERIFY : #ifdef _DEBUG #define VERIFY(x) if( (x) == 0) report_assert_failure() #else #define VERIFY(x) (x) #endif 这样的话,代码在release版中就能够执行了。 III. 参数问题: 自定义消息的处理函数,必须定义以下: afx_msg LRESULT OnMyMessage(WPARAM, LPARAM); 返回值必须是HRESULT型,不然Debug会过,而Release出错 IV. 内存分配 保证数据建立和清除的统一性:若是一个DLL提供一个可以建立数据的函数,那么这个DLL同时应该提供一个函数销毁这些数据。数据的建立和清除应该在同一个层次上。 V. DLL的灾难 人们将不一样版本DLL混合形成的不一致性形象的称为 “动态链接库的地狱“(DLL Hell) ,甚至微软本身也这么说(http://msdn.microsoft.com/library/techart/dlldanger1.htm)。 若是你的程序使用你本身的DLL时请注意: 1.不能将debug和release版的DLL混合在一块儿使用。debug都是debug版,release版都是release版。 解决办法是将debug和release的程序分别放在主程序的debug和release目录下 2.千万不要觉得静态链接库会解决问题,那只会使状况更糟糕。 VI. RELEASE板中的调试 : 1.将ASSERT() 改成 VERIFY() 。找出定义在"#ifdef _DEBUG"中的代码,若是在RELEASE版本中须要这些代码请将他们移到定义外。查找TRACE(...)中代码,由于这些代码在RELEASE中也不被编译。 请认真检查那些在RELEASE中须要的代码是否并无被便宜。 2.变量的初始化所带来的不一样,在不一样的系统,或是在DEBUG/RELEASE版本间都存在这样的差别,因此请对变量进行初始化。 3.是否在编译时已经有了警告?请将警告级别设置为3或4,而后保证在编译时没有警告出现. VII. 将Project Settings" 中 "C++/C " 项目下优化选项改成Disbale(Debug)。编译器的优化可能致使许多意想不到的错误,请参考http://www.pgh.net/~newcomer/debug_release.htm 1.此外对RELEASE版本的软件也能够进行调试,请作以下改动: 在"Project Settings" 中 "C++/C " 项目下设置 "category" 为 "General" 而且将"Debug Info"设置为 "Program Database"。 在"Link"项目下选中"Generate Debug Info"检查框。 "Rebuild All" 如此作法会产生的一些限制: 没法得到在MFC DLL中的变量的值。 必须对该软件所使用的全部DLL工程都进行改动。 另: MS BUG:MS的一份技术文档中代表,在VC5中对于DLL的"Maximize Speed"优化选项并未被彻底支持,所以这将会引发内存错误并致使程序崩溃
相关文章
相关标签/搜索