三国演义里面说过一句话:天下大事,合久必分,分久必合。有相聚,就有分离的时候。今天我们主要聊聊operator delete的故事
今天我们主要学习知识点:
1.delete的调用流程。
2.我们重载了delete之后能干啥。
3.placement delete有啥用。
测试代码如下:
/****************************************************************************
**
** Copyright (C) 2019 [email protected]
** All rights reserved.
**
****************************************************************************/
/*
测试对象的new、delete,在VS2017更容易观察
*/
#ifndef obj_new_delete_h
#define obj_new_delete_h
#include
#include
#include
using std::cout;
using std::endl;
namespace obj_new_delete
{
class Obj
{
public:
Obj():mCount(0) { cout << "Obj ctor" << endl; }
~Obj() { cout << "~Obj dtor" << endl; }
private:
int mCount;
};
void test_new_obj()
{
Obj *obj = new Obj();
delete obj;
}
}
#endif // obj_new_delete_h
老规矩,我们转到反汇编的代码:
00287340 mov eax,dword ptr [obj]
; delete obj
00287343 mov dword ptr [ebp-104h],eax
00287349 mov ecx,dword ptr [ebp-104h]
0028734F mov dword ptr [ebp-0F8h],ecx
; 如果ecx为0,不用调用operator delete和析构函数
00287355 cmp dword ptr [ebp-0F8h],0
0028735C je obj_new_delete::test_delete_obj+0D3h (0287373h)
0028735E push 1
00287360 mov ecx,dword ptr [ebp-0F8h]
; 析构代理函数
00287366 call obj_new_delete::Obj::`scalar deleting destructor' (0281212h)
0028736B mov dword ptr [ebp-10Ch],eax
00287371 jmp obj_new_delete::test_delete_obj+0DDh (028737Dh)
00287373 mov dword ptr [ebp-10Ch],0
obj_new_delete::Obj::`scalar deleting destructor':
00281212 jmp obj_new_delete::Obj::`scalar deleting destructor' (0282820h)
00282840 mov dword ptr [this],ecx
00282843 mov ecx,dword ptr [this]
; 调用对象的析构函数
00282846 call obj_new_delete::Obj::~Obj (02814C4h)
0028284B mov eax,dword ptr [ebp+8]
; 需要释放内存
0028284E and eax,1
00282851 je obj_new_delete::Obj::`scalar deleting destructor'+41h (0282861h)
00282853 push 4
00282855 mov eax,dword ptr [this]
; 传递对象的首地址放到eax寄存器中
00282858 push eax
; 调用局部的operator delete,第一参数为首地址,第二个参数为对象的大小
00282859 call operator delete (0281325h)
0028285E add esp,8
00282861 mov eax,dword ptr [this]
operator delete:
00281325 jmp operator delete (02832E0h)
; 调用全局的operator delete,只有一个参数为首地址
operator delete:
002811B3 jmp operator delete (02840C0h)
这里我顺便把operator delete的源码贴出来
// 局部operator delete源码
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block, size_t const) noexcept
{
operator delete(block);
}
// 全局operator delete源码
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
#ifdef _DEBUG
_free_dbg(block, _UNKNOWN_BLOCK);
#else
free(block);
#endif
}
根据这汇编代码我画出流程图:
看到了这张流程图,你一定对图中1,2有几点有疑问。
1.什么析构代理函数?
从名字中就可以看出就是个代理函数,它从中不仅调用我们自己写的析构函数,还做点其他幕后事情,比如流程图中的是否需要释放内存
2.为什么在流程图中还有个是否需要释放内存的判断?
大家请看这段代码,是否会释放内存。
void test_delete_obj()
{
Obj *obj = new Obj();
obj->~Obj();
}
如果我们自己手动调用了析构函数,这时系统是不会帮我们释放内存的。
我把上面的代码反汇编看下:
; obj->~Obj()
00EF7340 push 0
00EF7342 mov ecx,dword ptr [obj]
00EF7345 call obj_new_delete::Obj::`scalar deleting destructor' (0EF1212h)
可以看到这里首先push 0作为一个参数,push 0就表示仅仅调用析构函数,并不会释放内存。仔细看operator delete反汇编代码,这里push 1作为参数,表示需要释放内存,我在上面也做了注释。
3.为什么NULL指针,可以被delete多次。
因为C++运行时系统在delete就已经判断了,如果指针为空则不会调用delete。
最后我们需要注意下:
- 我们代码调用的析构函数,其实不是我们自己写的析构函数,而是编译器写的析构代理函数。
- 析构代理函数里面又做了其他的事,比如是否需要释放内存,再比如对象数组又是怎么释放内存。
只要我们重载了operator new,就应该对应的重载operator delete,他们两个是一一对应的东西。具体怎么重载的,在《我们来new个对象》中已经贴出代码了。
与placement new是个对应,前者是为了在原有内存上再次构造对象。后者是为了在异常负责回收内存。
在一般情况下,其实placement delete起不到作用的。只有在异常情况下,才会被C++运行时系统调用,用来释放内存。
具体怎么重载的,就不在多说了,在《我们来new个对象》中已经贴出代码了。
这个源码在vcruntime_new.h中
inline void __CRTDECL operator delete(void*, void*) noexcept
{
return;
}
恐怕会让你有点失望,在vs2017的版本中,这个什么里面都没有做的。搞的我也是摸不着头脑。
这节我们知道了operator delete调用流程,对对象的消失有了更深入的理解。同时知道了析构代理函数存在以及作用。
placement delete作用也是不容我们忽视的。