一件被误导很久的事:关于new和delete


当你写下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函数分配内存。有没有负责调用构造函数?这个真没有。。。

那构造函数到底是谁调用的?看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'里。

posted @ 2009-06-25 19:19  gussing  阅读(5281)  评论(3编辑  收藏  举报