debug与release的区别

一、Debug和Release编译方式的本质区别

 

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

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

 

Debug版本:

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

/Od关闭优化开关

/D"_DEBUG"相当于#define_DEBUG,打开编译调试代码开关(主要针对

assert函数)

/ZI创建Editandcontinue(编辑继续)数据库,这样在调试过

程中如果修改了源代码不需重新编译

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

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

 

Release版本:

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

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

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

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

被修改

 

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

 

二、哪些情况下Release版会出错

 

有了上面的介绍,我们再来逐个对照这些选项看看Release版错误是怎样产生的

 

1.RuntimeLibrary:链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的RuntimeLibrary包含了调试信息,并采用了一些保护机制以帮助发现错误,因此性能不如发布版本。编译器提供的RuntimeLibrary通常很稳定,不会造成Release版错误;倒是由于Debug的RuntimeLibrary加强了对错误的检测,如堆内存分配,有时会出现Debug有错但Release正常的现象。应当指出的是,如果Debug有错,即使Release正常,程序肯定是有Bug的,只不过可能是Release版的某次运行没有表现出来而已。

 

2.优化:这是造成错误的主要原因,因为关闭优化时源程序基本上是直接翻译的,而打开优化后编译器会作出一系列假设。这类错误主要有以下几种:

 

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

 

●MFC消息响应函数书写错误。正确的应为

afx_msgLRESULTOnMessageOwn(WPARAMwparam,LPARAMlparam);

ON_MESSAGE宏包含强制类型转换。防止这种错误的方法之一是重定义ON_MESSAGE宏,把下列代码加到stdafx.h中(在#include"afxwin.h"之后),函数原形错误时编译会报错

#undefON_MESSAGE

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

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

voidfn(void)

{

inti;

i=1;

inta[4];

{

intj;

j=1;

}

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

a[4]=1;

}

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

 

3._DEBUG与NDEBUG:当定义了_DEBUG时,assert()函数会被编译,而NDEBUG时不被编译。除此之外,VC++中还有一系列断言宏。这包括:

 

ANSIC断言voidassert(intexpression);

CRuntimeLib断言_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++的FindinFiles功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外,有些高手可能还会加入#ifdef_DEBUG之类的条件编译,也要注意一下。

   

     顺便值得一提的是VERIFY()宏,这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查WindowsAPI的返回值。有些人可能为这个原因而滥用VERIFY(),事实上这是危险的,因为VERIFY()违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻烦。因此,专家们建议尽量少用这个宏。

 

4./GZ选项:这个选项会做以下这些事

 

(1)初始化内存和变量。包括用0xCC初始化所有自动变量,0xCD(ClearedData)初始化堆中分配的内存(即动态分配的内存,例如new),0xDD(DeadData)填充已被释放的堆内存(例如delete),0xFD(deFencdeData)初始化受保护的内存(debug版在动态分配内存的前后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且32位系统中指针很少是奇数值,在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在Debug版中发现Release版才会遇到的错误。要特别注意的是,很多人认为编译器会用0来初始化变量,这是错误的(而且这样很不利于查找错误)。

(2)通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配)

(3)函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略FPO)

 

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

 

除此之外,/Gm/GF等选项造成错误的情况比较少,而且他们的效果显而易见,比较容易发现。

 

三、怎样“调试”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会带来很多冗余信息,如未使用的函数参数警告,而很多消息处理函数都会忽略某些参数。我们可以用

#progmawarning(disable:4702)//禁止

//...

#progmawarning(default:4702)//重新允许

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

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

//...

#progmawarning(pop)//重设为/W4

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

 

4.你也可以像Debug一样调试你的Release版,只要加入调试符号。在Project/Settings...中,选中Settingsfor"Win32Release",选中C/C++标签,Category选General,DebugInfo选ProgramDatabase。再在Link标签Projectoptions最后加上"/OPT:REF"(引号不要输)。这样调试器就能使用pdb文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到——这些都被优化过了。不过令人庆幸的是,CallStack窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。

 

————————————————————————————————————————

为什么Debug模式下编写的程序在Release模式下不能正常运行

这可以从下面的调试数据中看出。

 

程序如下:

#include"stdio.h"

 

staticintx;

voidHello()

{

_asm

{

moveax,x

mov[x],ebx

}

}

 

main(intargc,char*argv[])

{

charBuffer[512];

if(argc!=2)

{

printf("usgae:TestXXX\n");

return0;

}

Buffer[0]=’a’;

printf("Test!\n");

return0;

}

下面几个帖子是分析

 

————————————————————————————————————————

也不知道网上有没有类似的文章,小弟斗胆在这里献丑一回;

最近一段时间,许多人发帖子说自己的MFC程序Release版会出错,而Debug版不会出错,记得在两年前我也曾遇到过类似的问题,但是没有进行深入研究,这两天我对这个问题作了一个深入的探讨发现了一个非常容易犯的错误,这也与VC编译器有关(不知道是微软的BUG还是怎么回事),首先我们看一个事例工程:

用VC新建一个Dialog工程,然后加入一个新的对话窗,并且生成一个对话窗类;然后在主对话窗的OnOK事件中建立那个新对话窗的非模态对话窗,例如下面:

voidCADlg::OnOK()

{

m_pDlg=newCDlg1;//m_pDlg是类成员变量,新对话窗的指针

m_pDlg->Create(IDD_DIALOG1);

m_pDlg->ShowWindow(SW_SHOW);

}

 

然后加一个自定义消息:WM_MYMSG;

在新的对话窗的OnOK事件中向主对话窗发送WM_MYMSG消息;

voidCDlg1::OnOK()

{

CWnd*pWnd=GetParent();

pWnd->SendMessage(WM_MYMSG);

}

下面我要说的就是最关键的地方了,我们为了响应WM_MYMSG消息通常有两种做法,一种是重载主对话窗的WindowProc虚函数,然后在函数内部响应这个消息,例如下面:

LRESULTCADlg::WindowProc(UINTmessage,WPARAMwParam,LPARAMlParam)

{

Switch(message)

{

caseWM_MYMSG:

{

……

break;

}

}

returnCDialog::WindowProc(message,wParam,lParam);

}

这种做法通常不会出错;

下面我们看第二种响应这个消息的方法;

首先在主对话窗中加入一个函数,例如下面:

VOIDCADlg::OnMyMsg()

{

}

然后在主对话窗的消息映射表中加一项:

BEGIN_MESSAGE_MAP(CADlg,CDialog)

//{{AFX_MSG_MAP(CADlg)

ON_MESSAGE(WM_MYMSG,OnMyMsg)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

这样当我们的子对话窗向主对话窗发送WM_MYMSG消息的时候,MFC就会调用我们的OnMyMsg函数,于是错误出现了,首先我们看看ON_MESSAGE宏的定义;

#defineON_MESSAGE(message,memberFxn)\

{message,0,0,0,AfxSig_lwl,\

(AFX_PMSG)(AFX_PMSGW)(LRESULT(AFX_MSG_CALLCWnd::*)(WPARAM,LPARAM))&memberFxn}

熟悉宏定义的朋友一看就明白,这个宏在展开的时候实际上是将两个参数压栈(WPARAM和LPARAM),然后调用函数指针;

而我们的函数OnMyMsg确没有参数定义,换句话说,函数返回的时候不会平栈,这就是Release版程序非法操作的原因;

再说具体些,我们把上面的OnMyMsg函数写成这样:

VOIDCADlg::OnMyMsg()

{

MessageBox("测试");

}

然后看看它的汇编代码:

push0

push0

push403020h

call004017CA

ret

前面我们就不看了,看最后一句:

ret

完了,它直接ret了(当然了,直接ret是我们函数定义的结果),而不是比如说什么

ret8

之类的语句(这是因为我们的函数没有定义参数,因此直接ret)。

这样我们进入函数前压栈的两个参数就没有进行平栈动作了;函数返回,栈不平当然就会非法操作了;

换句话说,如果我们的程序写成这样:

VOIDCADlg::OnMyMsg()

{

MessageBox("测试");

__asmret8;

}

那么Release版就不会报错了(相反Debug版就会报错),不信请试验一下,但是这样写是不对的,请朋友们在编写程序的时候不要这样写,这只是说明消息映射函数平栈情况的一个证据罢了;

话分两头,为什么Debug版没有问题呢?

先看看下面的汇编:

movecx,dwordptr[ebp-0Ch]

movdwordptrfs:[0],ecx

popedi

popesi

popebx

addesp,5Ch

cmpebp,esp

call_chkesp(004022fc)

movesp,ebp

popebp

VC在对MFC的Debug版程序进行编译的时候,会在函数的后面加上一段类似上面的代码,那段代码的功能就是检测esp,看看栈是否是平的,如果不平则强行平栈,因此Debug版程序不会出这种错误,至于微软为什么要这样做,我实在也是想不明白,请各位朋友也一起琢磨一下吧(欢迎跟贴讨论);

综上所述,我们在编写MFC程序,映射自己的消息函数的时候要么采用第一种方法,重载WindowProc虚函数,要么采用第二种方法,但是函数要定义两个参数(WPARAM和LPARAM),即使没有用处也要这样定义;这样就可以避免Release版出错Debug版不出错的绝大部分情况了;

另外,我这里再提一下这个宏:

ON_MESSAGE_VOID

这个宏定义在"afxpriv.h",这个宏与ON_MESSAGE相反,他的消息映射函数不能带参数。即如果用这个宏进行消息映射,那么那个消息映射函数就不能带参数,如果带了参数就会发生Release版出错,Debug版不出错的情况了;

最后,我们不管用上面那个宏映射消息响应函数,而你的消息响应函数不管定义成什么样子,VC在进行编译的时候都不会报错,因此这个错误将隐藏的很深,直到你即将发布Release版的时候才发现,程序会非法操作的;

以上所述仅代表个人看法,如有不同意的朋友,欢迎参加讨论;

 

---------------------------------------------------------------

 

http://expert.csdn.net/Expert/topic/2539/2539864.xml?temp=.9315149

---------------------------------------------------------------

 

哈,vc7就会报错了,我还真不知道,看来微软也意识到这是个BUG了;

---------------------------------------------------------------

 

这是MFC的一个BUG,看看MSDN文档说的:

ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/vccore/html/vcrefwhatsnewlibrariesvisualc70.htm#vcrefwhatsnewlibrariesmfcvisualc70

 

VisualC++.NET中包含的下列库可能是新增的,也可能是经过更改的。

.......

Microsoft基础类(MFC)库

*有关MFC的参考主题包含数百个新的代码示例。

*静态强制类型转换和MFC消息映射从VisualC++.NET开始,MFC对消息处理函数的返回类型和参数类型进行更严格的类型检查。这些新增行为通过使用错误信息标记潜在不安全的消息处理函数,来通知开发人员可能会遇到的问题。MFC现对ON_MESSAGE、ON_REGISTERED_MESSAGE、ON_THREAD_MESSAGE和ON_REGISTERED_THREAD_MESSAGE使用静态强制类型转换。

例如,过去开发人员可以对ON_MESSAGE或ON_REGISTERED_MESSAGE使用返回void而非LRESULT的成员函数,并且编译时不报告任何错误。而使用VisualC++.NET,则可以捕获潜在的错误强制类型转换,并将它标记为错误。开发人员可以通过替换返回类型(用LRESULT替换)并重新编译来修复这种潜在的问题。

 

*DHTML编辑组件:CHtmlEditCtrl、CHTMLEditView、CHtmlEditDoc。

.........

 

VC7的界面用不惯,而且速度慢,否则值得升级。

 

---------------------------------------------------------------

 

对了,建议大家以后做东西的时候都用Release版本做,这样像上面的问题就不会出现,有的人

可能会说用Release版本怎么调试啊。下面的办法可以解决。调试完了改回原来的设置再发布

http://expert.csdn.net/Expert/TopicView1.asp?id=2555224

看看我的回答

 

Release版本单步跟踪方法:(可能对大家来说早就是小菜一碟了)

选中Win32Release然后

Project-》setting-》C/C++-》Category-》General

-》Optimization-》Disable(Debug)

-》DebugInfo-》ProgramDataBase

-》Link---》GenerateDebugInfo打上钩

 

另外请朋友们去捧捧这两个帖子。这个帖子我的回复花了我长时间。也许对那些

Debug和release不太懂得人有帮助。虽然不是我的帖子,但是所涉及到的知识

挺全的。

 

http://expert.csdn.net/Expert/TopicView1.asp?id=2539864

http://expert.csdn.net/Expert/topic/2553/2553540.xml?temp=.2079431

 

---------------------------------------------------------------

 

遇到并解决的第一个releasemodebug

 

“在ClassWizard添加的响应函数中使用手动添加的参数将导致Debug模式运行正常,但Release模式运行时非法操作。”

 

估计原因:MFC默认的ON_CONTROL消息响应函数原型为(void)pfn(void),因此在未改变MFC函数类型声明时,用额外的参数调用会导致Releasemode下,程序堆栈上的函数返回地址被作为函数参数来错误使用,而函数返回地址也就自然不对了,从而导致AccessViolation。

 

 

解决办法:

1.把消息响应函数声明对应的AfxSig_vv,改为相应函数类型的AfxSig_xx,然后用新的语句(原来的宏展开后把AfxSig_vv换成AfxSig_XX)替代ON_CONTROL等宏。

 

2.把消息响应函数的函数体移到一个自定义的一般类函数中,在类函数中使用参数,消息响应函数只对类函数进行调用。(此方法仅适用于在消息响应函数中添加默认参数的情况)

 

转自http://hi.baidu.com/vama/blog/item/6a823b47bdb3fa34879473ae.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值