int _tmain(int argc, _TCHAR* argv[])
{
A* p = new A[10];
delete []p;
return 0;
}
下面是生成的彙編代碼:
NewTest!wmain:
01181030 6a2c push 2Ch
01181032 e8c4030000 call NewTest!operator new[] (011813fb) //經過operator new分配44本身
01181037 83c404 add esp,4
0118103a 85c0 test eax,eax
0118103c 7444 je NewTest!wmain+0x52 (01181082)
0118103e 56 push esi
0118103f 6810101801 push offset NewTest!A::~A (01181010) //A的析構函數
01181044 6800111801 push offset NewTest!A::A (01181100) //A的構造函數
01181049 6a0a push 0Ah //10
0118104b 8d7004 lea esi,[eax+4] //跨過了頭四個字節
0118104e 6a04 push 4 //對象大小
01181050 56 push esi //esi裏放的是對象列表的起始地址(跨過了頭四個字節)
01181051 c7000a000000 mov dword ptr [eax],0Ah //頭四個字節寫入對象列表數量(10)
01181057 e812040000 call NewTest!`eh vector constructor iterator' (0118146e)
0118105c 85f6 test esi,esi
0118105e 7421 je NewTest!wmain+0x51 (01181081)
01181060 837efc00 cmp dword ptr [esi-4],0 //判斷對象數量是否 爲 0
01181064 8d46fc lea eax,[esi-4] //包含對象數量的地址保存到 eax
01181067 740f je NewTest!wmain+0x48 (01181078)
01181069 8b06 mov eax,dword ptr [esi] //取A的虛表地址
0118106b 8b5004 mov edx,dword ptr [eax+4] //虛表裏的第二個函數
0118106e 6a03 push 3
01181070 8bce mov ecx,esi
01181072 ffd2 call edx
01181074 5e pop esi
01181075 33c0 xor eax,eax
01181077 c3 ret
重點看上面
紅色
的代碼, 咱們能夠看到, 在new一個數組時,編譯器幫咱們作了下面一些事情:
(1)調用數組的operator new[] 分配內存, 大小爲 4 + sizeof(object) * count, 其中頭四個字節爲對象數量
(2)調用NewTest!`eh vector constructor iterator(pArrayAddress, sizeof(object), object_count, pFunConstructor, pFunDestructor),
其中 pFunDestructor爲析構函數, pFunConstructor爲構造函數, object_count爲對象數量, sizeof(object)爲對象大小,pArrayAddress爲起始地址。,
下面咱們反彙編 NewTest!`eh vector constructor iterator:
0:000> u 0118146e L50
NewTest!`eh vector constructor iterator':
0118146e 6a10 push 10h
01181470 6890221801 push offset NewTest!__rtc_tzz+0x8 (01182290)
01181475 e8d2040000 call NewTest!__SEH_prolog4 (0118194c)
0118147a 33c0 xor eax,eax
0118147c 8945e0 mov dword ptr [ebp-20h],eax
0118147f 8945fc mov dword ptr [ebp-4],eax
01181482 8945e4 mov dword ptr [ebp-1Ch],eax
01181485 8b45e4 mov eax,dword ptr [ebp-1Ch] //臨時計數,初始爲0
01181488 3b4510 cmp eax,dword ptr [ebp+10h] //將臨時計數和對象數量比較
0118148b 7d13 jge NewTest!`eh vector constructor iterator'+0x32 (011814a0) //若是臨時計數大於對象數量則退出循環
0118148d 8b7508 mov esi,dword ptr [ebp+8] //保存第一個參數(起始地址)到 esi
01181490 8bce mov ecx,esi //賦this指針到ecx
01181492 ff5514
call dword ptr [ebp+14h] //調用構造函數
01181495 03750c add esi,dword ptr [ebp+0Ch] //移動指針, 加上對象大小
01181498 897508 mov dword ptr [ebp+8],esi //保存新對象地址到第一個參數
0118149b ff45e4 inc dword ptr [ebp-1Ch] //增長臨時計數
0118149e ebe5 jmp NewTest!`eh vector constructor iterator'+0x17 (01181485)
011814a0 c745e001000000 mov dword ptr [ebp-20h],1
011814a7 c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh
011814ae e808000000 call NewTest!`eh vector constructor iterator'+0x4d (011814bb)
011814b3 e8d9040000 call NewTest!__SEH_epilog4 (01181991)
011814b8 c21400 ret 14h
咱們能夠看到NewTest!`eh vector constructor iterator是編譯器幫咱們生成的函數, 它的做用就是爲數組中的每一個對象都調用構造函數。
接下咱們再看看數組形式的delete []在背後究竟幹了什麼?
重點看上面
紫色
的代碼:
NewTest!wmain:
....
01181060 837efc00 cmp dword ptr [esi-4],0 //判斷對象數量是否 爲 0
01181064 8d46fc lea eax,[esi-4] //包含對象數量的地址保存到 eax
01181067 740f je NewTest!wmain+0x48 (01181078)
01181069 8b06 mov eax,dword ptr [esi] //取A的虛表地址
0118106b 8b5004 mov edx,dword ptr [eax+4] //虛表裏的第二個函數
0118106e 6a03 push 3
01181070 8bce mov ecx,esi
01181072 ffd2 call edx
....
能夠看到它將對象列表起始地址保存到ecx, 而後調用對象虛表裏的第二個函數, 而且傳入參數是3, 咱們先看對象虛表內容:
0:000> dps 01182148
01182148 01181000 NewTest!A::print [f:\test\newtest\newtest\newtest.cpp @ 11]
0118214c 01181090 NewTest!A::`vector deleting destructor'
咱們看看該函數究竟幹了什麼:
0:000> u 01181090 L40
NewTest!A::`vector deleting destructor':
01181090 53 push ebx
01181091 8a5c2408 mov bl,byte ptr [esp+8]
01181095 56 push esi
01181096 8bf1 mov esi,ecx
01181098 f6c302 test bl,2 //是否須要調用析構函數
0118109b 742b je NewTest!A::`vector deleting destructor'+0x38 (011810c8)
0118109d 8b46fc mov eax,dword ptr [esi-4]
011810a0 57 push edi
011810a1 6810101801 push offset NewTest!A::~A (01181010)
011810a6 8d7efc lea edi,[esi-4]
011810a9 50 push eax
011810aa 6a04 push 4
011810ac 56 push esi
011810ad e87f040000
call NewTest!`eh vector destructor iterator'
(01181531)
011810b2 f6c301 test bl,1 //是否須要釋放內存
011810b5 7409 je NewTest!A::`vector deleting destructor'+0x30 (011810c0)
011810b7 57 push edi
011810b8 e85f030000
call NewTest!operator delete[] (0118141c)
011810bd 83c404 add esp,4
011810c0 8bc7 mov eax,edi
011810c2 5f pop edi
011810c3 5e pop esi
011810c4 5b pop ebx
011810c5 c20400 ret 4
能夠看到它內部調用的是NewTest!`eh vector destructor iterator, 而若是再跟蹤NewTest!`eh vector destructor iterator,
會看全部數組裏的對象調用析構函數, 最後調用operator delete[]釋放全部內存。
咱們能夠看到數組new[]和delete[]的關鍵是, C++編譯器在數組起始地址以前的4個字節保存了對象的數量N,後面會根據這個數量值進行N次的構造和析構 。
最後申明下, 上面的分析僅限於VS2008, 實際上在符合C++標準的前提下, 各個C++編譯器有各自不一樣的實現。
咱們能夠看到C++ 編譯器在背後幹了不少事情,可能會內聯咱們的函數, 也能夠修改和產生其餘一些函數, 而這是不少C開發者受不了的事情, 因此在內核級別, 不少人寧願用C來減小編譯器背後的干擾。
最後思考一下, 若是咱們代碼這樣寫,會怎麼樣?
int _tmain(int argc, _TCHAR* argv[])
{
A* p = new
B
[10];
delete []p;
return 0;
}