现在的位置: 首页 > 综合 > 正文

从逆向分析角度看C++的析构函数

2012年09月19日 ⁄ 综合 ⁄ 共 4271字 ⁄ 字号 评论关闭

有这么一段C++代码:

#include <cstdio>

class CExample {
  int x, y;
  
public:
  CExample(int va, int vb) {
    x = va;
	y = vb;
  }
  CExample(const CExample& C) {
    x = C.x;
	y = C.y;
  }
  ~CExample() {
	printf("~CExample()\n");
  }
  
  void Show () {
    printf("%d, %d\n", x, y);
	return ;
  }
};

void fun(CExample E) {
  printf("In F(CExample)\n");
  return ;
}

int main() {
  CExample A(100, 200);
  CExample B = A;
  B.Show();
  fun(A);
  return 0;
}


运行结果:


编译调试的环境是 VC 6.0,注释如下:

main 函数:

30:   int main() {
00401130   push        ebp
00401131   mov         ebp,esp
00401133   push        0FFh
00401135   push        offset __ehhandler$_main (00413422)
0040113A   mov         eax,fs:[00000000]
00401140   push        eax
00401141   mov         dword ptr fs:[0],esp
00401148   sub         esp,5Ch
0040114B   push        ebx
0040114C   push        esi
0040114D   push        edi
0040114E   lea         edi,[ebp-68h]
00401151   mov         ecx,17h
00401156   mov         eax,0CCCCCCCCh
0040115B   rep stos    dword ptr [edi]				; 以上代码为建立堆栈,EBP = 1245000
31:     CExample A(100, 200);
0040115D   push        0C8h					; 将 200 压入堆栈中
00401162   push        64h					; 将 100 压入堆栈中
00401164   lea         ecx,[ebp-14h]				; EBP-14h 是 A 的内存地址,占 8 个字节,将该地址保存到 ECX 寄存器中
00401167   call        @ILT+0(CExample::CExample) (00401005)	; 调用构造函数 CExample(int, int)
0040116C   mov         dword ptr [ebp-4],0
32:     CExample B = A;
00401173   lea         eax,[ebp-14h]				; 将 A 的内存地址保存到 EAX 寄存器中
00401176   push        eax					; 将 EAX 压入堆栈中
00401177   lea         ecx,[ebp-1Ch]				; EBP-1Ch 是 B 的内存地址,占 8 个字节,将该地址保存到 ECX 寄存器中
0040117A   call        @ILT+20(CExample::CExample) (00401019)	; 调用拷贝构造函数,将A中的值复制到 B 的内存空间中,			

								; 细心观察一下栈地址就能明白
0040117F   mov         byte ptr [ebp-4],1
33:     B.Show();
00401183   lea         ecx,[ebp-1Ch]
00401186   call        @ILT+25(CExample::Show) (0040101e)
34:     fun(A);
0040118B   sub         esp,8					; ESP = 1244884, ESP - 8 = 1244876;ESP-8 是开辟的一块内存,
								; 在调用fun之前,先调用拷贝构造函数对 A 进行拷贝,拷贝的内容就
								; 存放在这块内存空间中(一个CExample对象,记为Temp)
0040118E   mov         ecx,esp					; ECX 寄存器保存 Temp 对象的内存地址
00401190   mov         dword ptr [ebp-20h],esp			; EBP-20h 这块空间保存 Temp 对象的内存地址
00401193   lea         edx,[ebp-14h]				; EDX 寄存器保存 A 对象的内存地址
00401196   push        edx					; ESP = 1244872,将 A 的内存地址压入栈
00401197   call        @ILT+20(CExample::CExample) (00401019)	; ESP = 1244868,函数返回地址压入栈,调用拷贝构造函数
0040119C   mov         dword ptr [ebp-28h],eax			; ESP = 1244876, EBP - 28h = 1244876,保存 Temp 的内存地址
0040119F   call        @ILT+15(fun) (00401014)			; ESP = 1244872,函数返回地址压入栈,调用fun函数
004011A4   add         esp,8					; __cdecl 规则,恢复堆栈段,EBP = 1245000
35:     return 0;
004011A7   mov         dword ptr [ebp-24h],0
004011AE   mov         byte ptr [ebp-4],0
004011B2   lea         ecx,[ebp-1Ch]				; EBP - 1Ch 是 B 的内存地址,保存到 ECX 中
004011B5   call        @ILT+5(CExample::~CExample) (0040100a)	; 调用析构函数,销毁对象 B
004011BA   mov         dword ptr [ebp-4],0FFFFFFFFh
004011C1   lea         ecx,[ebp-14h]				; EBP - 14h 是 A 的内存地址,保存到 ECX 中
004011C4   call        @ILT+5(CExample::~CExample) (0040100a)	; 调用析构函数,销毁对象 A
004011C9   mov         eax,dword ptr [ebp-24h]
36:   }

拷贝构造函数 CExample(const CExample& C):

11:     CExample(const CExample& C) {
00401270   push        ebp
00401271   mov         ebp,esp
00401273   sub         esp,44h
00401276   push        ebx
00401277   push        esi
00401278   push        edi
00401279   push        ecx
0040127A   lea         edi,[ebp-44h]
0040127D   mov         ecx,11h
00401282   mov         eax,0CCCCCCCCh
00401287   rep stos    dword ptr [edi]			; 以上为建立堆栈代码,EBP = 1244864
00401289   pop         ecx				; 恢复 ECX 寄存器的内容,即为 Temp 的内存地址
0040128A   mov         dword ptr [ebp-4],ecx		; EBP - 4 = 1244860,这块栈内存保存 Temp 的内存地址
12:       x = C.x;
0040128D   mov         eax,dword ptr [ebp-4]		; EBP - 4 = 1244860,EAX 获得 Temp 的内存地址(EAX 指向 Temp)
00401290   mov         ecx,dword ptr [ebp+8]		; EBP + 8 = 1244872,ECX 获得 A 的内存地址(ECX 指向 A)
00401293   mov         edx,dword ptr [ecx]		; EDX = 100(A 的首地址是 100,100 后于 200 进栈)
00401295   mov         dword ptr [eax],edx		; Temp.x = 100
13:       y = C.y;
00401297   mov         eax,dword ptr [ebp-4]
0040129A   mov         ecx,dword ptr [ebp+8]
0040129D   mov         edx,dword ptr [ecx+4]		; EDX = 200
004012A0   mov         dword ptr [eax+4],edx		; 同理,Temp.y = 200
14:     }
004012A3   mov         eax,dword ptr [ebp-4]		; EAX 寄存器保存 Temp 的内存空间
004012A6   pop         edi
004012A7   pop         esi
004012A8   pop         ebx
004012A9   mov         esp,ebp
004012AB   pop         ebp				; 恢复EBP,EBP = 1245000
004012AC   ret         4				; 恢复这一函数的堆栈段

fun 函数:

25:   void fun(CExample E) {
00401050   push        ebp
00401051   mov         ebp,esp							
00401053   push        0FFh
00401055   push        offset __ehhandler$?fun@@YAXVCExample@@@Z (004133f9)
0040105A   mov         eax,fs:[00000000]
00401060   push        eax
00401061   mov         dword ptr fs:[0],esp
00401068   sub         esp,40h
0040106B   push        ebx
0040106C   push        esi
0040106D   push        edi
0040106E   lea         edi,[ebp-4Ch]
00401071   mov         ecx,10h
00401076   mov         eax,0CCCCCCCCh
0040107B   rep stos    dword ptr [edi]
0040107D   mov         dword ptr [ebp-4],0				; EBP = 1244868,以上为建立堆栈代码
26:     printf("In F(CExample)\n");
00401084   push        offset string "In F(CExample)\n" (0042501c)
00401089   call        printf (00401320)
0040108E   add         esp,4
27:     return ;
00401091   mov         dword ptr [ebp-4],0FFFFFFFFh
00401098   lea         ecx,[ebp+8]					; EBP+8 = 1244876,是 Temp 的内存地址
									; ECX 寄存器保存
0040109B   call        @ILT+5(CExample::~CExample) (0040100a)		; 调用析构函数,销毁 Temp 对象(生命期结束)
28:   }
004010A0   mov         ecx,dword ptr [ebp-0Ch]
004010A3   mov         dword ptr fs:[0],ecx
004010AA   pop         edi
004010AB   pop         esi
004010AC   pop         ebx
004010AD   add         esp,4Ch
004010B0   cmp         ebp,esp
004010B2   call        __chkesp (00401780)
004010B7   mov         esp,ebp
004010B9   pop         ebp						; 恢复EBP,EBP = 1245000
004010BA   ret

下面重点分析一下调用 fun 函数时程序的工作机制:



执行“fun(A);”时,首先先调用拷贝构造函数,创建一个实体对象Temp,占8个字节长度的栈空间,内容是拷贝
A 的内容;然后再把对象 Temp 的内存地址压入 fun 函数的堆栈中,调用 fun 函数,当 fun 函数结束时,调用析构函数销毁 Temp 对象,从此 Temp 对象所占的栈空间被回收

抱歉!评论已关闭.