读书人

C++实用技巧七

发布时间: 2008-12-10 09:17:56 作者: liuhuituzi

主要谈谈vc里面函数调用汇编成汇编代码的情形,首先针对之前的一个小程序,说说vc编译器的优化。

  例子程序:

  #include

  using namespace std;

  int main(int argc, char* argv[])

  {

  int i=10;

  int a = i;

  cout << "i=" << a << endl;

  //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道

  __asm

  {

  mov dword ptr [ebp-4], 20h

  }

  int b = i;

  cout << "i=" << b << endl;

  return 0;

  }

  这段代码很简洁,但汇编代码可不简洁,首先看看release模式下的汇编代码概况:

  ……

  ; 14 :

  ; 15 : //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道

  ; 16 : __asm

  ; 17 : {

  ; 18 : mov dword ptr [ebp-4], 20h

  mov DWORD PTR [ebp-4], 32 ; 00000020H

  ; 19 : }

  ; 20 : int b = i;

  ; 21 : cout << "i=" << b << endl;

  push 10 ; 0000000aH

  push OFFSET FLAT:??_C@_02HDOK@i?$DN?$AA@ ; `string’

  push OFFSET FLAT:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A ; std::cout

  call ??6std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<

  add esp, 8

  mov ecx, eax

  call ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z ; std::basic_ostream >::operator<<

  mov esi, eax

  push 10 ; 0000000aH

  mov ecx, esi

  call ?put@?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV12@D@Z ; std::basic_ostream >::put

  mov ecx, DWORD PTR [esi]

  xor edi, edi

  ……

  调用cout前面,直接一个push 10,这是函数调用前压参数的过程,压了个常数在里面,呵呵,其实i已经被修改了,但是编译器不知道,以为i仍然是10,顾做了优化,考试,大提示压参压了常量在里面。

  再看看debug模式下的汇编代码情况:

  16: __asm

  17: {

  18: mov dword ptr [ebp-4], 20h

  004017DE mov dword ptr [ebp-4],20h

  19: }

  20: int b = i;

  004017E5 mov edx,dword ptr [ebp-4]

  004017E8 mov dword ptr [ebp-0Ch],edx

  21: cout << "i=" << b << endl;

  004017EB push offset @ILT+195(std::endl) (004010c8)

  004017F0 mov eax,dword ptr [ebp-0Ch]

  004017F3 push eax

  004017F4 push offset string "i=" (0046c01c)

  004017F9 push offset std::cout (00477a10)

  004017FE call @ILT+640(std::operator<<) (00401285)

  00401803 add esp,8

  00401806 mov ecx,eax

  b = i,赋值这句成了从i的地址去取值送往b了,

  mov edx,dword ptr [ebp-4]

  mov dword ptr [ebp-0Ch],edx

  注意release版本是没有这两句的,所以现在b取值就是肯定正确的了,后面压参的时候,也把b地址指向的值压入了堆栈,呵呵,这也是之前说的为什么两个版本下运行结果不同的原因。

  把这个程序稍加修改,开始我们下面的函数调用汇编浅析:

  #include

  using namespace std;

  int ChangNum(int, int);

  int main(int argc, char* argv[])

  {

  int i=10;

  int a = i;

  cout << "i=" << a << endl;

  //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道

  __asm

  {

  mov dword ptr [ebp-4], 20h

  }

  int b = i;

  cout << "i=" << b << endl;

  ChangNum(50, 100);

  return 0;

  }

  int ChangNum(int nParam, int nW)

  {

  int i = 10;

  int a = i;

  cout << "i=" << a << endl;

  __asm

  {

  mov dword ptr [ebp - 4], 20h

  mov dword ptr [ebp + 12], 0h

  }

  int b = i;

  cout << "i=" << b << endl;

  return 0;

  }

主要看看函数调用那段的汇编代码:

  1、函数调用点汇编代码:

  23: ChangNum(50, 100);

  00401824 push 64h

  00401826 push 32h

  00401828 call @ILT+590(ChangNum) (00401253)

  0040182D add esp,8

  分别是两个参数入栈,call这句有两个作用,下一行地址入栈,同时进行函数调用,最后一句是恢复栈空间,两个整型参数每个四字节,所以esp堆栈指针要加上8字节。

  2、函数体中的汇编代码:

  004018C0 push ebp

  004018C1 mov ebp,esp

  004018C3 sub esp,4Ch

  004018C6 push ebx

  004018C7 push esi

  004018C8 push edi

  004018C9 lea edi,[ebp-4Ch]

  004018CC mov ecx,13h

  004018D1 mov eax,0CCCCCCCCh

  004018D6 rep stos dword ptr [edi]

  30: int i = 10;

  004018D8 mov dword ptr [ebp-4],0Ah

  31: int a = i;

  004018DF mov eax,dword ptr [ebp-4]

  004018E2 mov dword ptr [ebp-8],eax

  32: cout << "i=" << a << endl;

  004018E5 push offset @ILT+195(std::endl) (004010c8)

  004018EA mov ecx,dword ptr [ebp-8]

  004018ED push ecx

  004018EE push offset string "i=" (0046c01c)

  004018F3 push offset std::cout (00477a10)

  004018F8 call @ILT+645(std::operator<<) (0040128a)

  004018FD add esp,8

  00401900 mov ecx,eax

  00401902 call @ILT+250(std::basic_ostream >::operator<<) (004010ff)

  00401907 mov ecx,eax

  00401909 call @ILT+475(std::basic_ostream >::operator<<) (004011e0)

  33:

  34: __asm

  35: {

  36: mov dword ptr [ebp - 4], 20h

  0040190E mov dword ptr [ebp-4],20h

  37: mov dword ptr [ebp + 12], 0h

  00401915 mov dword ptr [ebp+0Ch],0

  38: }

  39: int b = i;

  0040191C mov edx,dword ptr [ebp-4]

  0040191F mov dword ptr [ebp-0Ch],edx

  40: cout << "i=" << b << endl;

  00401922 push offset @ILT+195(std::endl) (004010c8)

  00401927 mov eax,dword ptr [ebp-0Ch]

  0040192A push eax

  0040192B push offset string "i=" (0046c01c)

  00401930 push offset std::cout (00477a10)

  00401935 call @ILT+645(std::operator<<) (0040128a)

  0040193A add esp,8

  0040193D mov ecx,eax

  0040193F call @ILT+250(std::basic_ostream >::operator<<) (004010ff)

  00401944 mov ecx,eax

  00401946 call @ILT+475(std::basic_ostream >::operator<<) (004011e0)

  41:

  42: return 0;

  0040194B xor eax,eax

  43: }

  0040194D pop edi

  0040194E pop esi

  0040194F pop ebx

  00401950 add esp,4Ch

  00401953 cmp ebp,esp

  00401955 call __chkesp (00406df0)

  0040195A mov esp,ebp

  0040195C pop ebp

  0040195D ret

  ebp入栈,然后将esp的值传给ebp,现在ebp是指向此时的堆栈了,注意后面除了函数返回前,ebp的值一直是固定的,通过这种机制来访问参数和局部变量,esp减少了一个比较大的值,留给局部变量使用的,然后是通用寄存器入栈,接着就是实际的工作的代码了,这里就不说了,到那个return后面再看,是通用寄存器出栈,esp恢复,ebp出栈,ret回到函数调用的下一条指令。



3COME考试频道为您精心整理,希望对您有所帮助,更多信息在http://www.reader8.net/exam/

读书人网 >复习指导

热点推荐