当你写下new和delete的时候,到底发生了什么事呢,让我们来做个试验看看。
写一段小代码:
- class a
- {
- public:
- a()
- {
- foo();
- }
- int foo()
- {
- return 0;
- }
- ~a()
- {
- bar();
- }
- int bar()
- {
- return 1;
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- a* tmp = new a();
- delete tmp;
- return 0;
- }
在main函数的第一句下断点,调试,然后开汇编窗口输出结果:
- int _tmain(int argc, _TCHAR* argv[])
- {
- 004113F0 push ebp
- 004113F1 mov ebp,esp
- 004113F3 push 0FFFFFFFFh
- 004113F5 push offset __ehhandler$_wmain (41478Eh)
- 004113FA mov eax,dword ptr fs:[00000000h]
- 00411400 push eax
- 00411401 sub esp,100h
- 00411407 push ebx
- 00411408 push esi
- 00411409 push edi
- 0041140A lea edi,[ebp-10Ch]
- 00411410 mov ecx,40h
- 00411415 mov eax,0CCCCCCCCh
- 0041141A rep stos dword ptr es:[edi]
- 0041141C mov eax,dword ptr [___security_cookie (418000h)]
- 00411421 xor eax,ebp
- 00411423 push eax
- 00411424 lea eax,[ebp-0Ch]
- 00411427 mov dword ptr fs:[00000000h],eax
- /*a* tmp = new a();*/
- 0041142D push 1
- 0041142F call operator new (4111A4h)
- 00411434 add esp,4
- 00411437 mov dword ptr [ebp-0F8h],eax
- 0041143D mov dword ptr [ebp-4],0
- 00411444 cmp dword ptr [ebp-0F8h],0
- 0041144B je wmain+70h (411460h)
- 0041144D mov ecx,dword ptr [ebp-0F8h]
- 00411453 call a::a (41101Eh)
- 00411458 mov dword ptr [ebp-10Ch],eax
- 0041145E jmp wmain+7Ah (41146Ah)
- 00411460 mov dword ptr [ebp-10Ch],0
- 0041146A mov eax,dword ptr [ebp-10Ch]
- 00411470 mov dword ptr [ebp-104h],eax
- 00411476 mov dword ptr [ebp-4],0FFFFFFFFh
- 0041147D mov ecx,dword ptr [ebp-104h]
- 00411483 mov dword ptr [ebp-14h],ecx
- /*delete tmp;*/
- 00411486 mov eax,dword ptr [ebp-14h]
- 00411489 mov dword ptr [ebp-0E0h],eax
- 0041148F mov ecx,dword ptr [ebp-0E0h]
- 00411495 mov dword ptr [ebp-0ECh],ecx
- 0041149B cmp dword ptr [ebp-0ECh],0
- 004114A2 je wmain+0C9h (4114B9h)
- 004114A4 push 1
- 004114A6 mov ecx,dword ptr [ebp-0ECh]
- 004114AC call a::`scalar deleting destructor' (41117Ch)
- 004114B1 mov dword ptr [ebp-10Ch],eax
- 004114B7 jmp wmain+0D3h (4114C3h)
- 004114B9 mov dword ptr [ebp-10Ch],0
- /*return 0;*/
- 004114C3 xor eax,eax
- }
- 004114C5 mov ecx,dword ptr [ebp-0Ch]
- 004114C8 mov dword ptr fs:[0],ecx
- 004114CF pop ecx
- 004114D0 pop edi
- 004114D1 pop esi
- 004114D2 pop ebx
- 004114D3 add esp,10Ch
- 004114D9 cmp ebp,esp
- 004114DB call @ILT+345(__RTC_CheckEsp) (41115Eh)
- 004114E0 mov esp,ebp
- 004114E2 pop ebp
- 004114E3 ret
前面一片调整stack,插入安全代码,设置异常处理等的操作不是今天我们要说的重点,直接跳到a* tmp = new a();这一句产生的反汇编:
- 0041142F call operator new (4111A4h)
我们很明确的看到调用了一个函数operator new。继续跟进operator new看到底做了什么事情:
- void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
- { // try to allocate size bytes
- void *p;
- while ((p = malloc(size)) == 0)
- if (_callnewh(size) == 0)
- { // report no memory
- static const std::bad_alloc nomem;
- _RAISE(nomem);
- }
- return (p);
- }
很意外吧,其实operator new函数就做了那么一件事情:调用malloc函数分配内存。有没有负责调用构造函数?这个真没有。。。
#p#
那构造函数到底是谁调用的?看operator new下面的那片汇编代码:
- 00411434 add esp,4
- 00411437 mov dword ptr [ebp-0F8h],eax
- 0041143D mov dword ptr [ebp-4],0
- 00411444 cmp dword ptr [ebp-0F8h],0
- 0041144B je wmain+70h (411460h)
- 0041144D mov ecx,dword ptr [ebp-0F8h]
- 00411453 call a::a (41101Eh)
出去将返回值赋给tmp的操作,我们看到了一处函数调用:
- 00411453 call a::a (41101Eh)
没错,对类a的构造函数的调用,是编译器偷偷在你的函数里插入的,当时的情况就是如此。delete的情况也是一摸一样。
再来看针对对象数组的new和delete:
- class a
- {
- public:
- a()
- {
- int i1;
- int j1 = 0;
- static int k1;
- static int l1 = 0;
- foo();
- }
- int foo()
- {
- return 0;
- }
- ~a()
- {
- int i2;
- int j2 = 0;
- static int k2;
- static int l2 = 0;
- bar();
- }
- int bar()
- {
- return 1;
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- a* tmp = new a[10];
- delete[] tmp;
- return 0;
- }
反汇编之后的结果如下:
- int _tmain(int argc, _TCHAR* argv[])
- {
- 004113F0 push ebp
- 004113F1 mov ebp,esp
- 004113F3 push 0FFFFFFFFh
- 004113F5 push offset __ehhandler$_wmain (41478Eh)
- 004113FA mov eax,dword ptr fs:[00000000h]
- 00411400 push eax
- 00411401 sub esp,100h
- 00411407 push ebx
- 00411408 push esi
- 00411409 push edi
- 0041140A lea edi,[ebp-10Ch]
- 00411410 mov ecx,40h
- 00411415 mov eax,0CCCCCCCCh
- 0041141A rep stos dword ptr es:[edi]
- 0041141C mov eax,dword ptr [___security_cookie (418000h)]
- 00411421 xor eax,ebp
- 00411423 push eax
- 00411424 lea eax,[ebp-0Ch]
- 00411427 mov dword ptr fs:[00000000h],eax
- a* tmp = new a[10];
- 0041142D push 0Eh
- 0041142F call operator new (4111A4h)
- 00411434 add esp,4
- 00411437 mov dword ptr [ebp-0F8h],eax
- 0041143D mov dword ptr [ebp-4],0
- 00411444 cmp dword ptr [ebp-0F8h],0
- 0041144B je wmain+97h (411487h)
- 0041144D mov eax,dword ptr [ebp-0F8h]
- 00411453 mov dword ptr [eax],0Ah
- 00411459 push offset a::`scalar deleting destructor' (41100Ah)
- 0041145E push offset a::a (41101Eh)
- 00411463 push 0Ah
- 00411465 push 1
- 00411467 mov ecx,dword ptr [ebp-0F8h]
- 0041146D add ecx,4
- 00411470 push ecx
- 00411471 call `eh vector constructor iterator' (4111F9h)
- 00411476 mov edx,dword ptr [ebp-0F8h]
- 0041147C add edx,4
- 0041147F mov dword ptr [ebp-10Ch],edx
- 00411485 jmp wmain+0A1h (411491h)
- 00411487 mov dword ptr [ebp-10Ch],0
- 00411491 mov eax,dword ptr [ebp-10Ch]
- 00411497 mov dword ptr [ebp-104h],eax
- 0041149D mov dword ptr [ebp-4],0FFFFFFFFh
- 004114A4 mov ecx,dword ptr [ebp-104h]
- 004114AA mov dword ptr [ebp-14h],ecx
- delete[] tmp;
- 004114AD mov eax,dword ptr [ebp-14h]
- 004114B0 mov dword ptr [ebp-0E0h],eax
- 004114B6 mov ecx,dword ptr [ebp-0E0h]
- 004114BC mov dword ptr [ebp-0ECh],ecx
- 004114C2 cmp dword ptr [ebp-0ECh],0
- 004114C9 je wmain+0F0h (4114E0h)
- 004114CB push 3
- 004114CD mov ecx,dword ptr [ebp-0ECh]
- 004114D3 call a::`vector deleting destructor' (4111F4h)
- 004114D8 mov dword ptr [ebp-10Ch],eax
- 004114DE jmp wmain+0FAh (4114EAh)
- 004114E0 mov dword ptr [ebp-10Ch],0
- return 0;
- 004114EA xor eax,eax
- }
- 004114EC mov ecx,dword ptr [ebp-0Ch]
- 004114EF mov dword ptr fs:[0],ecx
- 004114F6 pop ecx
- 004114F7 pop edi
- 004114F8 pop esi
- 004114F9 pop ebx
- 004114FA add esp,10Ch
- 00411500 cmp ebp,esp
- 00411502 call @ILT+345(__RTC_CheckEsp) (41115Eh)
- 00411507 mov esp,ebp
- 00411509 pop ebp
- 0041150A ret
其他部分都大同小异,关键的不同在编译器插入的,用于初始化的代码:
- 00411459 push offset a::`scalar deleting destructor' (41100Ah)
- 0041145E push offset a::a (41101Eh)
- 00411463 push 0Ah
- 00411465 push 1
- 00411467 mov ecx,dword ptr [ebp-0F8h]
- 0041146D add ecx,4
- 00411470 push ecx
- 00411471 call `eh vector constructor iterator' (4111F9h)
我们看到数组大小0Ah,构造函数的地址41101Eh都被压入栈中,作为某函数的参数。到底是什么函数呢?就是:
- 00411471 call `eh vector constructor iterator' (4111F9h)
一个名为`eh vector constructor iterator' 的函数。我们还注意到a类的析构函数的地址也被当成参数传入,这是干什么用的呢?构造函数里为什么要析构函数的地址?比如在遍历调用构造函数的过程中,前8个都是没问题的,到第9个突然资源不足调用失败了,那么在返回前无论如何也要先把前8个的析构函数调用一遍,防止资源泄露。
delete[]的过程也大同小异,不过一个很有趣的地方是,“vector deleting destructor'”是a类的成员函数,而与‘eh vector constructor iterator’对应的`eh vector destructor iterator'函数在“vector deleting destructor'”函数内部:
- 004134AD call `eh vector destructor iterator' (411203h)
。。。
- 004134C1 call operator delete (4110A0h)
回收内存的操作,也在a::`vector deleting destructor'里。
【编辑推荐】