有这么一段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 对象所占的栈空间被回收