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

//
// 备注:本篇文章来自 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;

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

?
1
2
3
4
5
6
7
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++代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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++代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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++ 各种学习资源,免费教程,期待您的加入!
//

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值