C++ new 和 delete
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只释放内存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步