当你写下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; }
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
在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
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
前面一片调整stack,插入安全代码,设置异常处理等的操作不是今天我们要说的重点,直接跳到a* tmp = new a();这一句产生的反汇编:
复制
0041142F call operator new (4111A4h)
1.
我们很明确的看到调用了一个函数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); }
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
很意外吧,其实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)
1.
2.
3.
4.
5.
6.
7.
出去将返回值赋给tmp的操作,我们看到了一处函数调用:
复制
00411453 call a::a (41101Eh)
1.
没错,对类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; }
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
反汇编之后的结果如下:
复制
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
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
其他部分都大同小异,关键的不同在编译器插入的,用于初始化的代码:
复制
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)
1.
2.
3.
4.
5.
6.
7.
8.
我们看到数组大小0Ah,构造函数的地址41101Eh都被压入栈中,作为某函数的参数。到底是什么函数呢?就是:
复制
00411471 call `eh vector constructor iterator' (4111F9h)
1.
一个名为`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)
1.
。。。
复制
004134C1 call operator delete (4110A0h)
1.
回收内存的操作,也在a::`vector deleting destructor'里。
【编辑推荐】