C++New和Delete
new和delete
-
使用new創建對象,delete銷毀對象
使用new創建一個動態類對象時,要執行三個步驟:
a)調用名為operator new的標准庫函數,分配足夠大的內存。
b)調用該類的一個構造函數,創建對象
c)返回執向該對象的指針
使用delete刪除時,要執行兩個步驟:
a)如果類對象有析構函數則調用析構
b)釋放類對象所占堆空間以下面CTest類為例,在VC++編譯器中觀察其行為:
class CTest { int m_nTest; public: CTest() { m_nTest = 1; } ~CTest() { m_nTest = 0; } };
測試代碼如下:
int main(int argc, char* argv[]) { CTest * p = new CTest; return 0; }
觀察其反匯編:
CTest * p = new CTest; 003A1ACD push 4 003A1ACF call operator new (03A138Eh) 003A1AD4 add esp,4 003A1AD7 mov dword ptr [ebp-0ECh],eax 003A1ADD mov dword ptr [ebp-4],0 003A1AE4 cmp dword ptr [ebp-0ECh],0 003A1AEB je main+70h (03A1B00h) 003A1AED mov ecx,dword ptr [ebp-0ECh] 003A1AF3 call CTest::CTest (03A10E1h) 003A1AF8 mov dword ptr [ebp-0F4h],eax 003A1AFE jmp main+7Ah (03A1B0Ah) 003A1B00 mov dword ptr [ebp-0F4h],0 003A1B0A mov eax,dword ptr [ebp-0F4h] 003A1B10 mov dword ptr [ebp-0E0h],eax 003A1B16 mov dword ptr [ebp-4],0FFFFFFFFh 003A1B1D mov ecx,dword ptr [ebp-0E0h] 003A1B23 mov dword ptr [p],ecx CTest * p = new CTest; 00BF1B9D push 4 00BF1B9F call operator new (0BF1398h) //調用new運算符,從堆中分配4字節內存 00BF1BA4 add esp,4 00BF1BA7 mov dword ptr [ebp-0ECh],eax 00BF1BAD mov dword ptr [ebp-4],0 00BF1BB4 cmp dword ptr [ebp-0ECh],0 //檢查內存是否分配成功,如果不成功則跳過構造函數的調用 00BF1BBB je main+70h (0BF1BD0h) 00BF1BBD mov ecx,dword ptr [ebp-0ECh] //傳遞this指針 00BF1BC3 call CTest::CTest (0BF10E1h) //內存分配成功后則調用CTest類的構造函數 00BF1BC8 mov dword ptr [ebp-10Ch],eax 00BF1BCE jmp main+7Ah (0BF1BDAh) 00BF1BD0 mov dword ptr [ebp-10Ch],0 00BF1BDA mov eax,dword ptr [ebp-10Ch] 00BF1BE0 mov dword ptr [ebp-0E0h],eax 00BF1BE6 mov dword ptr [ebp-4],0FFFFFFFFh 00BF1BED mov ecx,dword ptr [ebp-0E0h] 00BF1BF3 mov dword ptr [p],ecx //將構造完成后的指針賦值給p delete p; 00BF1BF6 mov eax,dword ptr [p] 00BF1BF9 mov dword ptr [ebp-104h],eax 00BF1BFF mov ecx,dword ptr [ebp-104h] 00BF1C05 mov dword ptr [ebp-0F8h],ecx 00BF1C0B cmp dword ptr [ebp-0F8h],0 00BF1C12 je main+0C9h (0BF1C29h) 00BF1C14 push 1 //析構函數標記,多重繼承時使用 00BF1C16 mov ecx,dword ptr [ebp-0F8h] //傳遞this指針 00BF1C1C call CTest::`scalar deleting destructor' (0BF1276h) //調用析構函數代理 00BF1C21 mov dword ptr [ebp-10Ch],eax 00BF1C27 jmp main+0D3h (0BF1C33h) 00BF1C29 mov dword ptr [ebp-10Ch],0
析構代理如下:
CTest::`scalar deleting destructor': 00BF1A90 push ebp 00BF1A91 mov ebp,esp 00BF1A93 sub esp,0CCh 00BF1A99 push ebx 00BF1A9A push esi 00BF1A9B push edi 00BF1A9C push ecx 00BF1A9D lea edi,[ebp-0CCh] 00BF1AA3 mov ecx,33h 00BF1AA8 mov eax,0CCCCCCCCh 00BF1AAD rep stos dword ptr es:[edi] 00BF1AAF pop ecx //還原this指針 00BF1AB0 mov dword ptr [this],ecx 00BF1AB3 mov ecx,dword ptr [this] 00BF1AB6 call CTest::~CTest (0BF123Fh) //調用類對象的析構函數 00BF1ABB mov eax,dword ptr [ebp+8] 00BF1ABE and eax,1 //標記,多重繼承時使用 00BF1AC1 je CTest::`scalar deleting destructor'+41h (0BF1AD1h) 00BF1AC3 push 4 //傳入對象大小 00BF1AC5 mov eax,dword ptr [this] 00BF1AC8 push eax //傳入對象地址 00BF1AC9 call operator delete (0BF1069h) //調用delete運算符釋放new運算符分配的對象 00BF1ACE add esp,8 00BF1AD1 mov eax,dword ptr [this] 00BF1AD4 pop edi 00BF1AD5 pop esi 00BF1AD6 pop ebx 00BF1AD7 add esp,0CCh 00BF1ADD cmp ebp,esp 00BF1ADF call __RTC_CheckEsp (0BF11E5h) 00BF1AE4 mov esp,ebp 00BF1AE6 pop ebp
可以看出VC++編譯器,為了實現C++標准中new和delete的行為,偷偷插入了不少代碼
使用new Type[]動態創建一個類對象的數組,要執行三個步驟:
a)調用名為operator new[]的標准庫函數,分配足夠大的內存。
b)調用該類的默認構造函數,創建數組中的每一個對象
c)返回對象數組的首地址測試代碼如下:
int main(int argc, char* argv[]) { CTest * p = new CTest[10]; delete[] p; return 0; }
對應反匯編代碼如下:
CTest * p = new CTest[10]; 00881C6D push 2Ch 00881C6F call operator new[] (088143Dh) //為數組分配空間 00881C74 add esp,4 00881C77 mov dword ptr [ebp-0ECh],eax 00881C7D mov dword ptr [ebp-4],0 00881C84 cmp dword ptr [ebp-0ECh],0 //判斷內存是否分配成功,不成功則跳過構造函數 00881C8B je main+97h (0881CC7h) 00881C8D mov eax,dword ptr [ebp-0ECh] 00881C93 mov dword ptr [eax],0Ah //將數組大小存入分配的堆空間前四個字節中 00881C99 push offset CTest::~CTest (0881258h) //傳入析構函數的地址作為構造代理函數的參數 00881C9E push offset CTest::CTest (08810E6h) //傳入構造函數的地址作為構造代理函數的參數 00881CA3 push 0Ah //傳入數組大小作為構造代理函數的參數 00881CA5 push 4 //傳入對象的大小作為構造代理函數的參數 00881CA7 mov ecx,dword ptr [ebp-0ECh] //取存放數組的堆地址空間 00881CAD add ecx,4 //跳過堆空間前4字節,定為到數組中首個對象的地址(見注解1) 00881CB0 push ecx //數組首地址入棧作為構造代理函數的參數 00881CB1 call `eh vector constructor iterator' (088119Ah) //調用構造代理函數 00881CB6 mov edx,dword ptr [ebp-0ECh] //調整存放數組對象的堆空間指針,使其加4指向數組中首對象的地址 00881CBC add edx,4 00881CBF mov dword ptr [ebp-10Ch],edx 00881CC5 jmp main+0A1h (0881CD1h) 00881CC7 mov dword ptr [ebp-10Ch],0 00881CD1 mov eax,dword ptr [ebp-10Ch] 00881CD7 mov dword ptr [ebp-0E0h],eax 00881CDD mov dword ptr [ebp-4],0FFFFFFFFh 00881CE4 mov ecx,dword ptr [ebp-0E0h] 00881CEA mov dword ptr [p],ecx //將數組首地址賦值給p
注解1:
一個CTest對象的大小為4,分配10個對象則總大小為40字節,轉成16進制就是0x38,但是從上面的反匯編代碼中可以看出
一共分配了0x3c個字節,多分配了4個字節,VC++編譯器用這四個字節來保存所分配數組的大小現在來看下構造代理函數的反匯編代碼:
00883960 push ebp 00883961 mov ebp,esp 00883963 push 0FFFFFFFEh 00883965 push 88C348h 0088396A push offset _except_handler4 (0884450h) 0088396F mov eax,dword ptr fs:[00000000h] 00883975 push eax 00883976 add esp,0FFFFFFECh 00883979 push ebx 0088397A push esi 0088397B push edi 0088397C mov eax,dword ptr [__security_cookie (088D004h)] 00883981 xor dword ptr [ebp-8],eax 00883984 xor eax,ebp 00883986 push eax 00883987 lea eax,[ebp-10h] 0088398A mov dword ptr fs:[00000000h],eax 00883990 mov dword ptr [i],0 //初始化for循環計數器 00883997 mov byte ptr [success],0 0088399B mov dword ptr [ebp-4],0 008839A2 jmp `eh vector constructor iterator'+4Dh (08839ADh) 008839A4 mov eax,dword ptr [i] /**************************下面代碼為for循環主體*************************** 008839A7 add eax,1 008839AA mov dword ptr [i],eax 008839AD mov ecx,dword ptr [i] 008839B0 cmp ecx,dword ptr [count] //判斷是否全部構造完成 008839B3 je `eh vector constructor iterator'+74h (08839D4h) 008839B5 mov edx,dword ptr [constructor] 008839B8 mov dword ptr [ebp-24h],edx 008839BB mov ecx,dword ptr [ebp-24h] 008839BE call @_guard_check_icall@4 (088144Ch) 008839C3 mov ecx,dword ptr [ptr] //傳遞this指針 008839C6 call dword ptr [ebp-24h] //調用構造函數 008839C9 mov eax,dword ptr [ptr] //ptr指向數組中下一個未構造的對象 008839CC add eax,dword ptr [size] 008839CF mov dword ptr [ptr],eax 008839D2 jmp `eh vector constructor iterator'+44h (08839A4h) //進行下一次循環 ************************************************************************/ 008839D4 mov byte ptr [success],1 008839D8 mov dword ptr [ebp-4],0FFFFFFFEh 008839DF call `eh vector constructor iterator'+86h (08839E6h) 008839E4 jmp $LN12 (0883A04h) $LN11: 008839E6 movzx ecx,byte ptr [success] 008839EA test ecx,ecx 008839EC jne `eh vector constructor iterator'+0A3h (0883A03h) 008839EE mov edx,dword ptr [destructor] 008839F1 push edx 008839F2 mov eax,dword ptr [i] 008839F5 push eax 008839F6 mov ecx,dword ptr [size] 008839F9 push ecx 008839FA mov edx,dword ptr [ptr] 008839FD push edx 008839FE call __ArrayUnwind (0881109h) $LN13: 00883A03 ret $LN12: 00883A04 mov ecx,dword ptr [ebp-10h] 00883A07 mov dword ptr fs:[0],ecx 00883A0E pop ecx 00883A0F pop edi 00883A10 pop esi 00883A11 pop ebx 00883A12 mov esp,ebp 00883A14 pop ebp 00883A15 ret 14h
再來看看析構:
delete[] p; 00881CED mov eax,dword ptr [p] 00881CF0 mov dword ptr [ebp-104h],eax 00881CF6 mov ecx,dword ptr [ebp-104h] 00881CFC mov dword ptr [ebp-0F8h],ecx 00881D02 cmp dword ptr [ebp-0F8h],0 //判斷指針p是否為空,不為空則執行析構 00881D09 je main+0F0h (0881D20h) 00881D0B push 3 //傳入析構標志:1表示析構單個對象,3表示析構數組,0表示僅執行析構不釋放對象所占堆空間 00881D0D mov ecx,dword ptr [ebp-0F8h] //數組首地址通過ecx傳遞 delete[] p; 00881D13 call CTest::`vector deleting destructor' (088121Ch) //調用析構代理函數 00881D18 mov dword ptr [ebp-10Ch],eax 00881D1E jmp main+0FAh (0881D2Ah) 00881D20 mov dword ptr [ebp-10Ch],0
析構代理函數如下:
CTest::`vector deleting destructor': 00881AC0 push ebp 00881AC1 mov ebp,esp 00881AC3 push 0FFFFFFFFh 00881AC5 push 8884D0h 00881ACA mov eax,dword ptr fs:[00000000h] 00881AD0 push eax 00881AD1 sub esp,0CCh 00881AD7 push ebx 00881AD8 push esi 00881AD9 push edi 00881ADA push ecx 00881ADB lea edi,[ebp-0D8h] 00881AE1 mov ecx,33h 00881AE6 mov eax,0CCCCCCCCh 00881AEB rep stos dword ptr es:[edi] 00881AED pop ecx //恢復數組首地址到ecx寄存器中 00881AEE mov eax,dword ptr [__security_cookie (088D004h)] 00881AF3 xor eax,ebp 00881AF5 push eax 00881AF6 lea eax,[ebp-0Ch] 00881AF9 mov dword ptr fs:[00000000h],eax 00881AFF mov dword ptr [this],ecx 00881B02 mov eax,dword ptr [ebp+8] //取出調用時傳入的釋放標記參數 00881B05 and eax,2 00881B08 je CTest::`vector deleting destructor'+8Eh (0881B4Eh) 00881B0A push offset CTest::~CTest (0881258h) //類對象的析構函數地址入棧 00881B0F mov eax,dword ptr [this] //取數組首地址 00881B12 mov ecx,dword ptr [eax-4] //取數組前面四個字節內容,即數組中的元素個數 00881B15 push ecx //存放對象數組的堆空間地址入棧(包含存放元素個數的4字節空間) 00881B16 push 4 //對象大小入棧 00881B18 mov edx,dword ptr [this] 00881B1B push edx //數組首地址入棧 00881B1C call `eh vector destructor iterator' (0881311h) 00881B21 mov eax,dword ptr [ebp+8] 00881B24 and eax,1 00881B27 je CTest::`vector deleting destructor'+86h (0881B46h) //調用析構函數二次代理 00881B29 mov eax,dword ptr [this] 00881B2C mov ecx,dword ptr [eax-4] 00881B2F lea edx,[ecx*4+4] 00881B36 push edx 00881B37 mov eax,dword ptr [this] 00881B3A sub eax,4 00881B3D push eax 00881B3E call operator delete[] (088103Ch) //釋放存放數組的整個堆空間 00881B43 add esp,8 00881B46 mov eax,dword ptr [this] 00881B49 sub eax,4 00881B4C jmp CTest::`vector deleting destructor'+0AFh (0881B6Fh) 00881B4E mov ecx,dword ptr [this] 00881B51 call CTest::~CTest (0881258h) 00881B56 mov eax,dword ptr [ebp+8] 00881B59 and eax,1 00881B5C je CTest::`vector deleting destructor'+0ACh (0881B6Ch) 00881B5E push 4 00881B60 mov eax,dword ptr [this] 00881B63 push eax 00881B64 call operator delete (088106Eh) 00881B69 add esp,8 00881B6C mov eax,dword ptr [this] 00881B6F mov ecx,dword ptr [ebp-0Ch] 00881B72 mov dword ptr fs:[0],ecx 00881B79 pop ecx 00881B7A pop edi 00881B7B pop esi 00881B7C pop ebx 00881B7D add esp,0D8h 00881B83 cmp ebp,esp 00881B85 call __RTC_CheckEsp (08811F9h) 00881B8A mov esp,ebp 00881B8C pop ebp 00881B8D ret 4
析構函數二次代理:
00883A80 push ebp 00883A81 mov ebp,esp 00883A83 push 0FFFFFFFEh 00883A85 push 88C368h 00883A8A push offset _except_handler4 (0884450h) 00883A8F mov eax,dword ptr fs:[00000000h] 00883A95 push eax 00883A96 add esp,0FFFFFFECh 00883A99 push ebx 00883A9A push esi 00883A9B push edi 00883A9C mov eax,dword ptr [__security_cookie (088D004h)] 00883AA1 xor dword ptr [ebp-8],eax 00883AA4 xor eax,ebp 00883AA6 push eax 00883AA7 lea eax,[ebp-10h] 00883AAA mov dword ptr fs:[00000000h],eax 00883AB0 mov byte ptr [success],0 //使得ptr指向對象數組的尾部 00883AB4 mov eax,dword ptr [size] 00883AB7 imul eax,dword ptr [count] 00883ABB add eax,dword ptr [ptr] 00883ABE mov dword ptr [ptr],eax 00883AC1 mov dword ptr [ebp-4],0 /********下面代碼為for循環主體代碼,從數組中最后一個對象開始逐一為其調用析構函數********/ 00883AC8 mov ecx,dword ptr [count] 00883ACB mov dword ptr [ebp-24h],ecx 00883ACE mov edx,dword ptr [count] 00883AD1 sub edx,1 00883AD4 mov dword ptr [count],edx 00883AD7 cmp dword ptr [ebp-24h],0 00883ADB jbe `eh vector destructor iterator'+7Ch (0883AFCh) 00883ADD mov eax,dword ptr [ptr] 00883AE0 sub eax,dword ptr [size] 00883AE3 mov dword ptr [ptr],eax 00883AE6 mov ecx,dword ptr [destructor] 00883AE9 mov dword ptr [ebp-20h],ecx 00883AEC mov ecx,dword ptr [ebp-20h] 00883AEF call @_guard_check_icall@4 (088144Ch) 00883AF4 mov ecx,dword ptr [ptr] 00883AF7 call dword ptr [ebp-20h] //調用析構函數 00883AFA jmp `eh vector destructor iterator'+48h (0883AC8h) /********************************************************************/ 00883AFC mov byte ptr [success],1 00883B00 mov dword ptr [ebp-4],0FFFFFFFEh 00883B07 call `eh vector destructor iterator'+8Eh (0883B0Eh) 00883B0C jmp $LN11 (0883B2Ch) $LN10: 00883B0E movzx edx,byte ptr [success] 00883B12 test edx,edx 00883B14 jne `eh vector destructor iterator'+0ABh (0883B2Bh) 00883B16 mov eax,dword ptr [destructor] 00883B19 push eax 00883B1A mov ecx,dword ptr [count] 00883B1D push ecx 00883B1E mov edx,dword ptr [size] 00883B21 push edx 00883B22 mov eax,dword ptr [ptr] 00883B25 push eax 00883B26 call __ArrayUnwind (0881109h) $LN12: 00883B2B ret $LN11: 00883B2C mov ecx,dword ptr [ebp-10h] 00883B2F mov dword ptr fs:[0],ecx 00883B36 pop ecx 00883B37 pop edi 00883B38 pop esi 00883B39 pop ebx 00883B3A mov esp,ebp 00883B3C pop ebp 00883B3D ret 10h
注意:在VC++中,如果類中有自定義的析構函數,則在返回對象數組前面會用一個四字節空間記錄數組大小,
如果沒有定義自己的析構函數則不會有這四個字節,原因應該是這樣的,因為如果有自定以的析構函數
在delete的時候就必須要調用析構函數來析構每一個對象,這樣做就必須得知道對象個數,如果沒有自
定義的析構函數,在delete的時候只要釋放所占用內存即可,不必調析構。
使用new和delete的注意事項
new和delete配套使用,new []應該和delete[]配套使用,從上面的分析來看如果new出來的單個對象,
使用delete[]釋放時會取該對象前4個字節的內容作為對象個數,然后執行對應次數的析構(如果類有析構函數),
然后在釋放堆空間,這樣做絕對會造成程序異常;如果一個new出來的對象數組使用delete釋放,只會析構並釋放數組中
首元素對象所占內存,造成內存泄漏和其它資源泄漏。
new和delete與malloc和free的區別
-
new和delete為C++中運算符,其原型如下:
void operator new[](size_t bytes);
void operator new(size_t bytes);
void operator delete(void* _Block);
void operator delete[](void* _Block);
new和delete可以重載,而malloc和free只是C語言的庫函數,和普通函數一樣,不是運算符 -
new不僅分配內存,還觸發類對象的構造函數,而malloc只是分配內存
delete先調用對象的析構函數(如果有析構函數),然后在是否對象所占內存,free只釋放內存