Debug和release

                                                                  Debug release 的区别

一.定义:

Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用

 

二.真正的区别:在于一组编译选项

 我们将默认的DebugRelease的选项设置进行比较,过滤掉相同设置,主要的不同如下:

Debug设置:
编译选项:/Od /D "_DEBUG" /Gm /RTC1 /MDd /Fo"Debug““" /ZI
链接选项:/OUT"D“MyProject“logging“Debug“OptionTest.dll" /INCREMENTAL
Release设置:
编译选项:/O2 /GL /D "NDEBUG" /FD /MD /Fo"Release““" /Zi
 接选项:/OUT"D“MyProject“logging“Release“OptionTest.dll" /INCREMENTALNO

 Debug 版本   
  参数       含义   
  /MDd /MLd 或 /MTd 使用 Debug runtime library(调试版本的运行时刻函数库)   
  /Od 关闭优化开关   
  /D "_DEBUG" 相当于 #define _DEBUG,打开编译调试代码开关(主要针对assert函数

      【注】assert函数:

       assert是指编写代码时做出一些假设,断言就是用于在代码中捕捉这些假设,可以 将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言  
  /ZI    创建 Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需  重新编译   
  GZ 可以帮助捕获内存错误 

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

 

 

Release 版本 参数含义   
  /MD /ML 或 /MT 使用发布版本的运行时刻函数库   
  /O1 或 /O2 优化开关,使程序最小或最快   
  /D "NDEBUG" 关闭条件编译调试代码开关(即不编译assert函数)   
  /GF 合并重复的字符串,并将字符串常量放到只读内存,防止被修改 

【注】

MDdMD
    首先,Debug版本使用调试版本的运行时库(/MDd选项),Relase版本则使用的是发布版本的运行时库(vcrt.dll)。其区别主要在于运行时的性能影响。调试版本的运行时库包含了调试信息,并采用了一些保护机制以帮助发现错误,也因此,其性能不如发布版本。编译器提供的Runtime Library很稳定,不会造成Release版本错误,倒是由于Debug版本的Runtime Library加强了对错误的检测,如堆内存分配检查等,反而会报告错误,应当指出,如果Debug有错误,而Release版本正常,程序肯定是有Bug的,只是我们还没有发现。


ZIZi
    其次,/ZI选项与/Zi选项。通过使用/ZI选项,可以在调试过程修改代码 而不需要重新编译。这是个调试的好帮手,可如果我们使用Release版本,这将变得不可行。

OdO2
    /O2/Od选项:Od是关闭编译器优化,普遍用于Debug版本。而O2选项是创建最快速代码,这当然是Release版本的不二选择。

RTCx 
     /RTCx选项让编译器插入动态检测代码以帮助你检测程序中的错误。比如,它会将局部变量初始化为非零值。包括用0xCC初始化所有自动变量,0xCD初始化堆中分配的内存(即new的内存),使用0xDD填充被释放的内存(即delete的内存),0xFD初始化受保护的内存(debug版在动态分配内存的前后加入保护内存以防止越界访问)。这样做的好处是这些值都很大,一般不可能作为指针,考试,大提示作为数值也很少用到,而且这些值很容易辩认,因此有利于在Debug版本中发现Release版才会遇到的错误。另外,通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性(防止原型不匹配)。使用/RTCx选项会造成Debug版本出错,而Release版本正常的现象,因为Release版中未初始化的变量是随机的,很可能使指针指向了有效但是错误的地址,从而掩盖了错误。这个编译选项只能在/Od选项下使用。

GmINCREMENTAL or NO
     编译选项中的Gm和链接选项中的INCREMENTAL都只为一个目的,加快编译速度。我们经常遇上这样的问题,只修改了一个头文件,结果却造成所有动态库的重新编译。而这两个选项就是为了解决这样的问题。如果启用了/Gm开关,编译器在项目中的.idb文件中存储了源文件和类定义之间的依赖关系。之后的编译过程中使用.idb文件中的信息确定是否需要编译某个源文件,哪怕是此源文件已经包含了已修改的.h文件。
     INCREMENTAL开关默认是开启的。使用增量链接生 成的可执行文件或者动态链接库会大于非增量链接的程序,因为有代码和数据的填充。另外,增量链接的文件还包含跳转trunk以处理函数重定位到新地址。
     MSDN 上明确指出:为确保最终发布版本不包含填充或者trunk,请非增量链接程序。

/GZ 选项:做以下这些事 
     1. 始化内存和变量。包括用 0xCC初始化所有自动变量,0xCD ( Cleared Data )初始化堆中分配的内存(即动态分配 的内存,例如 new),0xDD ( Dead Data )填充已被释放的堆内存(例  delete),0xFD( deFencde Data )初始化受保护的内存(debug版在动态分配内存的前后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且32 位系统中指针很少是奇数值, 在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在Debug 版中发 Release 版才会遇到的错误。要特别注意的是,很多人认为编译器会用来初始化变量,这是错误的(而且这样很不利于查找错 误)。
     2. 通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配) 
  3. 函数返回前检查 栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略FPO 

    通常 /GZ  项会造成Debug 版出错而Release 版正常的现象,因为Release 版中未初始化的变量是随机的,这有可能使指针指向一 个有效地址而掩盖了非法访问。

_DEBUGNDEBUG
     这是最重要的一个选项。这两个是编译器的预处理器定义,默认情况下_DEBUG用于Debug版本,而NDEBUG用于Release版本。 它们可以说是重要的无以复加。因为,assert系列的断言仅仅在_DEBUG下生效!
下面是assert.h文件中摘出来的:
C++代码 

[cpp] view plaincopy

1 #ifdef NDEBUG   

2 #define assert(_Expression) ((void)0)   

3 #else /* NDEBUG */   

4 #ifdef __cplusplus   

5 extern "C" {   

6 #endif /* __cplusplus */   

7 _CRTIMP void __cdecl _wassert(__in_z const wchar_t * _Message, __in_z const wchar_t *_File, __in unsigned _Line);  

8 #ifdef __cplusplus   

9 }   

10 #endif /* __cplusplus */   

11 #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )  

12 #endif /* NDEBUG */   

可以看出在未定义_DEBUG时,assert变成一条空语句不被执行。
也就是说,我们现在所有发布的版本无法使 用断言机制进行程序调试。

 

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

 

三..哪些情况下  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"之后),函数原形错误时编译会报错  

C++代码 

[cpp] view plaincopy

#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  版中,这个变量很可能被优化调,此时数组越界会破坏栈中有用的数据。当然,实际的情况会比这复杂得多。与此有关的错误有:  

●  非法访问,包括数组越界、指针错误等。例如  

C++代码 

[cpp] view plaincopy

void  fn(void)  

{  

int  i;  

i  =  1;  

int  a[4];  

{  

int  j;  

j  =  1;  

}  

a[-1]  =  1;//当然错误不会这么明显,例如下标是变量  

a[4]  =  1;  

}  

j  虽然在数组越界时已出了作用域,但其空间并未收回,因而  i    j  就会掩盖越界。而  Release  版由于  ij  并未其很大作用可能会被优化掉,从而使栈被破坏。  

 

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()  违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻烦。因此,专家们建议尽量少用这个宏。    

 

 

四.VC下关于debugrelease的不同的讨论

在使用VC开发软件的过程中,正当要享受那种兴奋的时候突然发现:releasedebug运行结果不一致,甚至出错,而release又不方便调试,真的是当头一棒啊,可是疼归疼,问题总要解决,下面将讲述一下我的几点经验,看看是不是其中之一:

1. 变量

大家都知道,debugrelease在初始化变量时所做的操作是不同的,debug是将每个字节位都赋成0xcc(1),而release的赋值近似于随机(我想是直接从内存中分配的,没有初始化过)。这样就明确了,如果你的程序中的某个变量没被初始化就被引用,就很有可能出现异常:用作控制变量将导致流程导向不一致;用作数组下标将会使程序崩溃;更加可能是造成其他变量的不准确而引起其他的错误。所以在声明变量后马上对其初始化一个默认的值是最简单有效的办法,否则项目大了你找都没地方找。代码存在错误在debug方式下可能会忽略而不被察觉到,如debug方式下数组越界也大多不会出错,在release中就暴露出来了,这个找起来就比较难了

2. 自定义消息的消息参数。

Release  Build不对源代码进行调试,不考虑MFC的诊断宏,使用的是MFC  Release库,编译十对应用程序的速度进行优化,而Debug  Build则正好相反,它允许对源代码进行调试,可以定义和使用MFC的诊断宏,采用MFC  Debug库,对速度没有优化。

 

MFC为我们提供了很好的消息机制,更增加了自定义消息,好处我就不用多说了。这也存在debugrelease的问题吗?答案是肯定的。在自定义消息的函数体声明时,时常会看到这样的写法:afx_msg LRESULT OnMessageOwn(); Debug情况下一般不会有任何问题,而当你在Release下且多线程或进程间使用了消息传递时就会导致无效句柄之类的错误。导致这个错误直接原因是消息体的参数没有添加,即应该写成:afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); (2)

3. release模式下不出错,但debug模式下报错。

这种情况下大多也是因为代码书写不正确引起的,查看MFC的源码,可以发现好多ASSERT的语句(断言),这个宏只是在debug模式下才有效,那么就清楚了,release版不报错是忽略了错误而不是没有错误,这可能存在很大的隐患,因为是Debug模式下,比较方便调试,好好的检查自己的代码,再此就不多说了。

4. ASSERT, VERIFY, TRACE……….调试宏

这种情况很容易解释。举个例子:请在VC下输入ASSERT然后选中按F12跳到宏定义的地方,这里你就能够发现DebugASSERT要执行AfxAssertFailedLine,而Release下的宏定义却为”#define ASSERT(f) ((void)0)”。所以注意在这些调试宏的语句不要用程序相关变量如i++写操作的语句。VERIFY是个例外,”#define VERIFY(f) ((void)(f))”,即执行,这里的作用就不多追究了

总结:

DebugRelease不同的问题在刚开始编写代码时会经常发生,99%是因为你的代码书写错误而导致的,所以不要动不动就说系统问题或编译器问题,努力找找自己的原因才是根本。我从前就常常遇到这情况,经历过一次次的教训后我就开始注意了,现在我所写过的代码我已经好久没遇到这种问题了。下面是几个避免的方面,即使没有这种问题也应注意一下:

1. 注意变量的初始化,尤其是指针变量,数组变量的初始化(很大的情况下另作考虑了)

2. 自定义消息及其他声明的标准写法

3. 使用调试宏时使用后最好注释掉

4. 尽量使用try - catch(…)

5. 尽量使用模块,不但表达清楚而且方便调试。

 

五.DEBUGRELEASE 版本差异及调试相关问题:

I. 内存分配问题

1. 变量未初始化。下面的程序在debug中运行的很好。

C++代码 

[cpp] view plaincopy

      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.  数据溢出的问题  

        如:

      C++代码

      char buffer[10];

             int counter;

          lstrcpy(buffer, "abcdefghik");

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

 

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

 

II.      ASSERTVERIFY

1. ASSERTRelease版本中是不会被编译的。

ASSERT宏是这样定义的

    C++代码 

        #ifdef _DEBUG

        #define ASSERT(x) if( (x) == 0) report_assert_failure()

        #else

        #define ASSERT(x)

        #endif

        实际上复杂一些,但无关紧要。假如你在这些语句中加了程序中必须要有的代码

比如

C++代码 

ASSERT(pNewObj = new CMyClass);

pNewObj->MyFunction();

这种时候Release版本中的pNewObj不会分配到空间

所以执行到下一个语句的时候程序会报该程序执行了非法操作的错误。这时可以用

C++代码 

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同时应该提供一个函数销毁这些数据。数据的创建和清除应该在同一个层次上。

如果是基于ATL的,则DebugRelease版本对DLL的要求差不多。如果采用的编译选项为使用MFC动态库,则需要MFC42D.DLL等库支持,而Release版本需要MFC42.DLL支持

 

V.     DLL的灾难

人们将不同版本DLL混合造成的不一致性形象的称为 “动态连接库的地狱“(DLL Hell) ,甚至微软自己也这么说(http://msdn.microsoft.com/library/techart/dlldanger1.htm)

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

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

解决办法是将debugrelease的程序分别放在主程序的debugrelease目录下

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

 

VI.  RELEASE板中的调试 

1.         ASSERT() 改为 VERIFY() 。找出定义在"#ifdef _DEBUG"中的代码,如果在RELEASE版本中需要这些代码请将他们移到定义外。查找TRACE(...)中代码,因为这些代码在RELEASE中也不被编译。 请认真检查那些在RELEASE中需要的代码是否并没有被便宜。

2.         变量的初始化所带来的不同,在不同的系统,或是在DEBUG/RELEASE版本间都存在这样的差异,所以请对变量进行初始化。

3.         是否在编译时已经有了警告?请将警告级别设置为34,然后保证在编译时没有警告出现.

 

 

VII.   Project Settings"  "C++/C " 项目下优化选项改为DisbaleDebug)。编译器的优化可能导致许多意想不到的错误,请参考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 BUGMS的一份技术文档中表明,在VC5中对于DLL"Maximize Speed"优化选项并未被完全支持,因此这将会引起内存错误并导致程序崩溃。

2.         www.sysinternals.com有一个程序DebugView,用来捕捉OutputDebugString的输出,运行起来后(估计是自设为system debugger)就可以观看所有程序的OutputDebugString的输出。此后,你可以脱离VC来运行你的程序并观看调试信息。 

3.         有一个叫Gimpel Lint的静态代码检查工具,据说比较好用。

 

 

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值