文档章节

对VC++下Debug模式和Release模式的简要分析

V雪落有声V
 V雪落有声V
发布于 2015/06/11 17:56
字数 2904
阅读 45
收藏 0
点赞 0
评论 0

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 备注:本篇文章来自 vc驿站:http://www.cctry.com/thread-254205-1-1.html
// C、C++、VC++ 各种学习资源,免费教程,期待您的加入!
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

对于VC++的Debug模式与Release模式区别的简要介绍
                                                                 QQ:446989572

看到很多像我一样的新手,对于Debug模式和Release模式区分不好
或者只是有着模糊的认识
所以,简单的写了这个分析,希望能帮助这些朋友加强区分。
另外声明本人也只是小菜一个,如果有什么说的不对的地方,还希望大家给我指出
共同交流,一起进步


一说到Debug模式,与Release模式的区别,可能大家最先想到:编程的时候使用Debug,
而完成了检验了没有错误后发行使用Release。总体上来说这是没错的,但是为什么会这样呢?
或者说两个模式有什么样的特性才让大家有了这样的认识呢?

首先从Debug模式的设计理念上来说明,Debug顾名思义就是调试的意思。
就是编译器为了方便使用者调试自己的程序而设计的模式
在工程的设置中可以看到,Debug和Release的很多设值、开关都不同

其实Debug最早在DOS编程的时候,作为唯一的调试方式广为大家熟知

Debug的基本原理,就是调用操作系统提供的调试接口,通过设下中断的方式
让程序代码执行后马上将控制权移交回Debug器手里并暂时中断
就处于这么运行、暂停、运行、暂停。。。的状态下
从而达到动态的查看数值,控制程序走向的目的

既然是调用系统的函数,那么我们就可以通过相关API检查自己程序的运行状态是否正在被调试
比如最简单的:函数原型为   

BOOL WINAPI IsDebuggerPresent(void);

如果当前进程运行在一个用户调试器环境下,返回非零(也就是True)
否则返回0

这也是反调试技术的最初级形态和核心
(当然一个程序在被调试的时候,N多的标示值都会随之改变,所以获知自己是否被调试的方法也是多种多样,这里不再做过多说明)

那么,Debug模式究竟是怎么与Release区别以方便我们调试和维护呢?

主要区别在于检查和优化

我们先来说说检查:

可能我们在刚开始学习C语言的时候,听到过这么个理论:
当我们申请一个变量,但是并不给它初始化的时候,这个变量的值是随机的

这主要是因为CPU工作方式和内存读写方式决定的:
在变量销毁后,其值并没有消失,而是依然留在内存中,直到一个新的写入将它覆盖。
所以,我们得到的变量,在内存空间中的位置可能以前就被使用过,所以得到的值也是未知的。

上述理论并没有错误,然而,在VC++的Debug模式下,这个理论暂时不适用

为了增强程序的健壮性与容错率,Debug模式的编译器会在未初始化的变量申请后,自行的初始化它们,

值就是0xcc(一个字节)
所以我们使用如下代码:
int a;
得到的a值是固定的,a=0xCCCCCCCC;

下面给出一段简单的代码证实:

int _tmain(int argc, _TCHAR* argv[])
{
        int a;                                
        int b;                        
        b=0x1234abcf;  //只是便于在汇编代码中查找的常量赋值
        return 0;
}

下面是Debug版的汇编代码:

  1. _tmain(void)

  2. {

  3. 00E01370 >  55              push ebp                                 ; 常规的函数入口操作

  4. 00E01371    8BEC            mov ebp,esp

  5. 00E01373    81EC D8000000   sub esp,0xD8                             ; 这个值很大,为什么呢?

  6. 00E01379    53              push ebx

  7. 00E0137A    56              push esi

  8. 00E0137B    57              push edi

  9. 00E0137C    8DBD 28FFFFFF   lea edi,dword ptr ss:[ebp-0xD8]          ; 从edi开始,循环0x36次,填入0xCC

  10. 00E01382    B9 36000000     mov ecx,0x36

  11. 00E01387    B8 CCCCCCCC     mov eax,0xCCCCCCCC

  12. 00E0138C    F3:AB           rep stos dword ptr es:[edi]

  13. 00E0138E    C745 EC CFAB341>mov dword ptr ss:[ebp-0x14],0x1234ABCF   ; b=0x1234ABCF

  14. 00E01395    33C0            xor eax,eax                             

  15. 00E01397    5F              pop edi                                

  16. 00E01398    5E              pop esi                                 

  17. 00E01399    5B              pop ebx                                

  18. 00E0139A    8BE5            mov esp,ebp

  19. 00E0139C    5D              pop ebp                                 

  20. 00E0139D    C3              retn

好,从上面的汇编代码我们不难看出,不止变量a,b,0xD8这样大的空间都被赋值0xCC了
而编译器自作主张,将栈顶抬高一个明显我们程序用不到的空间高度(0xD8),目的就是加强程序的健壮性和可调试性
也是为了我们好。。。所以Debug版的程序无论大小还是运行速度都与Release版的有差别,原因就在这里了

那么,有的人就有了疑问,0xCC是什么?为什么要赋值为0xCC?为什么不是0xBB或者0xFF?
原因很简单,0xCC 对应的OpCode(汇编操作码)为int 3  
当然这里的int不是那个C语言中的整数类型Integer,是种普通的中断
int 3就是我们调试器常用的中断

所以我们就能看出,这样做以后,如果程序因为某些错误(通常都是因为指针越界)导致程序代码执行
(也就是EIP)到了不该去的地方

这满屏幕的int 3就能马上将程序中断下来,而不至于导致严重的错误。

其实,上下翻动看看,我们这个Main函数就好像int 3海洋中的一个小孤岛——————早已被满屏的int 3包围了

这样大家就不难理解Debug模式的强大之处了吧~~

好吧。。。实际上,Debug编译器为我们做的,远比我们看到的多得多

比如,在我们的正常函数调用中,你会发现:
首先,是C++代码:

int func(int a,int b)
{
        int temp;
        temp=a+b;
        return temp;
}

int _tmain(int argc, _TCHAR* argv[])
{
        int a;
        int b;
        a=0x123;
        b=0x1234abcf;
        int AddNum=func(a,b);
        printf("%d",AddNum);
        return 0;
}

只是简单的加法,奇异的数字只是为了便于寻找代码定位
好了,下面贴出部分汇编代码:

  1. 009217C0 >  55              push ebp

  2. 009217C1    8BEC            mov ebp,esp

  3. 009217C3    81EC E4000000   sub esp,0xE4

  4. 009217C9    53              push ebx

  5. 009217CA    56              push esi

  6. 009217CB    57              push edi

  7. 009217CC    8DBD 1CFFFFFF   lea edi,dword ptr ss:[ebp-0xE4]          ; 这些地方没什么变化

  8. 009217D2    B9 39000000     mov ecx,0x39

  9. 009217D7    B8 CCCCCCCC     mov eax,0xCCCCCCCC

  10. 009217DC    F3:AB           rep stos dword ptr es:[edi]

  11. 009217DE    C745 F8 2301000>mov dword ptr ss:[ebp-0x8],0x123

  12. 009217E5    C745 EC CFAB341>mov dword ptr ss:[ebp-0x14],0x1234ABCF

  13. 009217EC    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]

  14. 009217EF    50              push eax

  15. 009217F0    8B4D F8         mov ecx,dword ptr ss:[ebp-0x8]

  16. 009217F3    51              push ecx

  17. 009217F4    E8 BFF9FFFF     call Just_A_T.009211B8       

执行加法 :

  1. 009217F9    83C4 08         add esp,0x8

  2. 009217FC    8945 E0         mov dword ptr ss:[ebp-0x20],eax

  3. 009217FF    8BF4            mov esi,esp

  4. 00921801    8B45 E0         mov eax,dword ptr ss:[ebp-0x20]

  5. 00921804    50              push eax

  6. 00921805    68 3C579200     push offset Just_A_T.??_C@_02DPKJAMEF@?$>; ASCII "%d"

  7. 0092180A    FF15 B4829200   call dword ptr ds:[<&MSVCR90D.printf>]   ; printf

  8. 00921810    83C4 08         add esp,0x8

  9. 00921813    3BF4            cmp esi,esp                              ; 这里有个奇怪的比较

  10. 00921815    E8 B2F9FFFF     call Just_A_T.009211CC                   ; 重点在这里

  11. 0092181A    33C0            xor eax,eax

  12. 0092181C    5F              pop edi                                 

  13. 0092181D    5E              pop esi                                  

  14. 0092181E    5B              pop ebx                                  

  15. 0092181F    81C4 E4000000   add esp,0xE4

  16. 00921825    3BEC            cmp ebp,esp                              ; 这里同样有个奇怪比较

  17. 00921827    E8 A0F9FFFF     call Just_A_T.009211CC                   ; 重点也是这里

  18. 0092182C    8BE5            mov esp,ebp

  19. 0092182E    5D              pop ebp                                  

  20. 0092182F    C3              retn

大家可能也注意到了上面两个奇怪的cmp和call,这是干什么呢?我们明明没有需要比较的步骤啊?
好吧,不吊胃口了,这其实也是Debug编译器为我们所做的事情,它们用于检查esi和ebp的值是否正确
call就是调用了检查的函数。。。
而上述的所有检查在Release模式中都是没有的。

那么,通过上面的说明,你是否已经被Debug模式的贴心和强大所征服了?   O(∩_∩)O

好,接下来说说优化,这也是Debug与Release的一个重大区别

正常情况下,Debug模式是很少进行优化的
而Release却会使用很高级别的优化,以加强程序的运行速度与效率

那么我就简单的举个例子好了。

按照惯例,先是C++代码:

int _tmain(int argc, _TCHAR* argv[])
{
        int a;
        int b;
        a=0x123;
        b=0x1234abcf;
        if (b>a)
        {
                printf("B是大于A的呦~");
        }
        else
        {
                printf("B竟然比A小!");
        }
        return 0;
}

由于使用的Debug模式,所以汇编代码也没有变化:

  1. 00A32DB0 >  55              push ebp

  2. 00A32DB1    8BEC            mov ebp,esp

  3. 00A32DB3    81EC D8000000   sub esp,0xD8

  4. 00A32DB9    53              push ebx

  5. 00A32DBA    56              push esi

  6. 00A32DBB    57              push edi

  7. 00A32DBC    8DBD 28FFFFFF   lea edi,dword ptr ss:[ebp-0xD8]

  8. 00A32DC2    B9 36000000     mov ecx,0x36

  9. 00A32DC7    B8 CCCCCCCC     mov eax,0xCCCCCCCC

  10. 00A32DCC    F3:AB           rep stos dword ptr es:[edi]

  11. 00A32DCE    C745 F8 2301000>mov dword ptr ss:[ebp-0x8],0x123

  12. 00A32DD5    C745 EC CFAB341>mov dword ptr ss:[ebp-0x14],0x1234ABCF

  13. 00A32DDC    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]

  14. 00A32DDF    3B45 F8         cmp eax,dword ptr ss:[ebp-0x8]           ; 对A与B进行比较

  15. 00A32DE2    7E 19           jle short Just_A_T.00A32DFD              ; 如果b<a就跳转(当然是不可能的)

  16. 00A32DE4    8BF4            mov esi,esp

  17. 00A32DE6    68 0858A300     push offset Just_A_T.??_C@_0O@MCPPOFAI@B>; “B大于A呦~”

  18. 00A32DEB    FF15 B482A300   call dword ptr ds:[<&MSVCR90D.printf>]   ; 调用printf

  19. 00A32DF1    83C4 04         add esp,0x4

  20. 00A32DF4    3BF4            cmp esi,esp                              ; 这两条依然是对esi的检查

  21. 00A32DF6    E8 D1E3FFFF     call Just_A_T.00A311CC                   ; 贴心的Debug

  22. 00A32DFB    EB 17           jmp short Just_A_T.00A32E14              ; 执行完成后就跳出if语句

  23. 00A32DFD    8BF4            mov esi,esp                              ; 如果上面B<A,就跳到这里

  24. 00A32DFF    68 A057A300     push offset Just_A_T.??_C@_0N@ELAPGMA@B?>; “B竟然小于A!”

  25. 00A32E04    FF15 B482A300   call dword ptr ds:[<&MSVCR90D.printf>]   ; 调用printf

  26. 00A32E0A    83C4 04         add esp,0x4

  27. 00A32E0D    3BF4            cmp esi,esp

  28. 00A32E0F    E8 B8E3FFFF     call Just_A_T.00A311CC

  29. 00A32E14    33C0            xor eax,eax

  30. 00A32E16    5F              pop edi

  31. 00A32E17    5E              pop esi

  32. 00A32E18    5B              pop ebx

  33. 00A32E19    81C4 D8000000   add esp,0xD8

  34. 00A32E1F    3BEC            cmp ebp,esp

  35. 00A32E21    E8 A6E3FFFF     call Just_A_T.00A311CC

  36. 00A32E26    8BE5            mov esp,ebp

  37. 00A32E28    5D              pop ebp

  38. 00A32E29    C3              retn


考虑到很多朋友看不懂汇编代码,我加入了比较详细的注释,应该没有问题了吧

可以看到,Debug版的汇编代码忠实的还原了我们程序的判断流程,虽然我们都能看出有一个判断分支是

永远不能到达的

所以说Debug就像那个爱着你,所以原谅你的小贪心小糊涂的好男人(女人),看透一切却什么都不说,

默默做事却从来都不邀功。。。
(*^__^*) 

那么,我们来看看Release版的汇编代码。。。。


  1. 00891156   > \A1 1C308900   mov eax,dword ptr ds:[envpcurity_cookie_complement]

  2. 0089115B   .  8B0D 80208900 mov ecx,dword ptr ds:[<&MSVCR90.__winitenv>]         ;  msvcr90.__winitenv

  3. 00891161   .  8901          mov dword ptr ds:[ecx],eax                          

  4. 00891163   .  FF35 1C308900 push dword ptr ds:[envpcurity_cookie_complement]

  5. 00891169   .  FF35 20308900 push dword ptr ds:[argvxceptionPointerssonement]

  6. 0089116F   .  FF35 18308900 push dword ptr ds:[argctxitbeginerredtableement]

  7. 00891175   .  E8 86FEFFFF   call Just_A_T.wmainerrinitgeBasexceptionFilter                 ;调用printf


  8. 00891000 >/$  68 F4208900   push offset Just_A_T.??_C@_0O@MCPPOFAI@B?J?G?$LE?s?S>; /format = "B是大于A的呦~"

  9. 00891005  |.  FF15 A0208900 call dword ptr ds:[<&MSVCR90.printf>]                ; \printf

  10. 0089100B  |.  83C4 04       add esp,0x4

  11. 0089100E  |.  33C0          xor eax,eax                                         

  12. 00891010  \.  C3            retn

尽管做好了心理准备,但是我还是差点没认出我的代码来 (⊙﹏⊙)b)。。。。
我的0x1234abcf呢?我的“B竟然小于A”呢?

从00891000这里我们可以看出Release版本的优化能力来:


它在行进编译的时候,判断出来我们的那个if语句的第二个分支是使用常量来判断的
所以第二分支是不可能到达的
所以。。。它就残忍的剪掉了我们的代码,当然这样也达到了我们代码的运行速度极限。

这就是Release版本优化的魅力和威力的一个小小的缩影

QQ:446989572
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 备注:本篇文章来自 vc驿站:http://www.cctry.com/thread-254205-1-1.html
// C、C++、VC++ 各种学习资源,免费教程,期待您的加入!
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

本文转载自:http://www.cctry.com/thread-254205-1-1.html

共有 人打赏支持
V雪落有声V
粉丝 0
博文 2
码字总数 0
作品 0
包头
程序员
C++ primer第二次阅读学习笔记(第6章)

第六章 在switch中只有在最后一个case或是default语句之后才能定义变量,之所以这样规定是为了避免代码跳过变量定义和初始化。 如switch(val) { case xx: break; case yy: int i;//错误,该变...

长平狐
2012/10/08
72
0
为什么栈中的数组越界后经常不崩溃,还可以正常的操作呢

使用数组也是家常便饭,但是经常出现越界使用数组也能编译通过的奇怪问题,和我们书中学习的很不一样,怎么折腾也不知道为什么,郁闷了吧!哈哈哈,别慌,这里就给你答案啦。 函数中分配的数...

hejunbinlan
2016/11/29
20
0
C#调C++生成的dll报0x800736B1错误

这个错误看上去好像是 C++ DLL库本身的问题,其实并不尽然。由于使用了混合模式编译托管 DLL ,所以该 DLL 又会用到非托管的 CRT ( C Run-Time )库。如果机器上没有安装这些被使用到的运行时...

Yong_Luo
2011/08/13
0
0
BoundsChecker用法

最近我发现我那个程序泄露严重,手动也查不出来。指针乱指。所以刚下了个BoundChecker 6.0 版的 嘿嘿ie 在网上看到了它的用法,感觉还不错。 BoundsChecker用法 http://blog.sina.com.cn/u/...

cnbird
2010/12/24
0
0
openCV vs2013配置

让曾经也是小白的我来写一个VS一次配置永久使用OpenCV的终极小白教程: 说白了就是将VS第一次配置OpenCV的项目属性表保存下来,每次新建项目时引用即可。 目录 解压OpenCV 配置系统环境变量 ...

文剑Boy
2016/03/29
0
0
Debug与Release的区别

VC下Debug和Release区别 最近写代码过程中,发现 Debug 下运行正常,Release 下就会出现问题,百思不得其解,而Release 下又无法进行调试,于是只能采用printf方式逐步定位到问题所在处,才发...

尤加辉
2012/12/26
0
0
VS2008生成静态库(lib)文件

静态链接库的创建 在solution中 new project ->win32 project -> static library 即可创建生成静态链接库的项目。 在该项目的properties中可看到: Configuration Properties -->General --......

cosmos180
2012/07/16
0
0
找不到MSVCR90.dll、Debug vs Release及cppLapack相关

最近使用cppLapack,nmake运行正常,在Release模式下也编译运行正常,在Debug模 式下也能编译通过,但一运行就弹出一个对话框,提示没有找到MSVCR90D.dll,程序未能启 动,而如果把该DLL拷贝...

ryany
2010/12/27
0
0
VS下 debug与release运行结果不一致

网罗了大量文章,主要说变量未初始化的较多,代码量较大,着实不好搞,依次排查,但凡涉及的都已经初始化,无果... eggs hurt ~持续~ 最后初步敲定嫌疑:PI的问题,每次用到介个全局变量的时...

Industryy
2015/01/05
0
0
Debug与Release版本的区别详解

Debug 和 Release 并没有本质的区别,他们只是VC预定义提供的两组编译选项的集合,编译器只是按照预定的选项行动。如果我们愿意,我们完全可以把Debug和Release的行为完全颠倒过来。当然也可...

长平狐
2012/10/08
927
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

数据库事务的四大特性以及事务的隔离级别

本篇讲述数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别。 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity) 原子性是...

Java大蜗牛
14分钟前
0
0
Spring Boot 整合 MyBatis/通用Mapper/PageHelper分页插件

整合MyBatis 整合通用Mapper 1. POM依赖配置 <properties><mapper.starter.version>2.0.3-beta1</mapper.starter.version></properties><!-- 通用Mapper --><dependency><groupId>t......

OSC_fly
23分钟前
0
0
CentOS7 双网卡绑定

环境 操作系统 CentOS7.5,禁用 NetworkManager 服务 网卡 eth0 网卡 eth1 绑定网卡 bond0 网卡 eth0 配置 修改 /etc/sysconfig/network-scripts/ifcfg-eth0 TYPE=EthernetBOOTPROTO=noneD......

Colben
25分钟前
0
0
zk实战--rpc框架集群化

在看此篇内容时需要浏览下面内容 netty实战--手写rpc框架 前文功能简介以及功能扩充 利用netty来实现一个点对点的rpc调用。客户端和服务端都是靠手写地址进行socket同学的,无法1对多,也无法...

xpbob
41分钟前
12
0
springboot 发送邮件

获取授权码 添加配置 # 账号和密码spring.mail.username=aaa@qq.comspring.mail.password=bbb# 服务器地址spring.mail.host=smtp.qq.comspring.mail.properties.mail.smtp.ssl.en...

阿豪boy
41分钟前
0
0
如何使用GNU Ring?

文章名:如何使用GNU Ring? 作者:冰焰火灵X 1079092922@qq.com 文章许可:CC BY-SA 4.0 ##1. 安装 下载GNU Ring 点击左边选择你的系统版本(这里以 GNU/Linux 为例,我使用的是Mint 18.3)...

ICE冰焰火灵X
44分钟前
4
0
深入理解springMVC

什么是spring MVC Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而...

Java填坑之路
50分钟前
2
0
《射雕英雄传》书摘

1. 我虽是个飘泊江湖的贫家女子,可不是低三下四、不知自爱之人。你如真心爱我,须当敬我重我。我此生决无别念,就是钢刀架颈,也决意跟定了你。将来……将来如有洞房花烛之日,自然……自能...

k91191
今天
1
0
解决:modal中datePicker 选中时,会触发modal的hidden.bs.modal事件

最近项目中发现了一个bug,具体表现为选中模态框上datepicker组件上的日期时,会触发模态框的关闭事件,导致数据编辑无法正常进行。网上搜索了下,解决方法如下: $('.datepicker').on('hid...

Funcy1122
今天
0
0
Redis分布式锁的正确实现方式

前言 分布式锁一般有三种实现方式: 1.数据库乐观锁 2.基于Redis的分布式锁; 3.基于Zookeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis...

大海201506
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部