C++幕後故事(十)–對象離我們而去
三國演義裏面說過一句話:天下大事,合久必分,分久必合。有相聚,就有分離的時候。今天我們主要聊聊operator delete的故事
今天我們主要學習知識點:
1.delete的調用流程。
2.我們重載了delete之後能幹啥。
3.placement delete有啥用。
1. operator delete操作符的原理
1.1 operator delete 調用流程
測試代碼如下:
/****************************************************************************
**
** Copyright (C) 2019 [email protected]
** All rights reserved.
**
****************************************************************************/
/*
測試對象的new、delete,在VS2017更容易觀察
*/
#ifndef obj_new_delete_h
#define obj_new_delete_h
#include <new>
#include <memory>
#include <iostream>
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。
最後我們需要注意下:
- 我們代碼調用的析構函數,其實不是我們自己寫的析構函數,而是編譯器寫的析構代理函數。
- 析構代理函數裏面又做了其他的事,比如是否需要釋放內存,再比如對象數組又是怎麼釋放內存。
1.2 重載delete操作符
只要我們重載了operator new,就應該對應的重載operator delete,他們兩個是一一對應的東西。具體怎麼重載的,在《我們來new個對象》中已經貼出代碼了。
2. placement delete
2.1 什麼是placement delete?
與placement new是個對應,前者是爲了在原有內存上再次構造對象。後者是爲了在異常負責回收內存。
2.2 placement delete作用
在一般情況下,其實placement delete起不到作用的。只有在異常情況下,纔會被C++運行時系統調用,用來釋放內存。
2.3 placement delete重載
具體怎麼重載的,就不在多說了,在《我們來new個對象》中已經貼出代碼了。
2.4 一窺系統的placement delete源碼
這個源碼在vcruntime_new.h中
inline void __CRTDECL operator delete(void*, void*) noexcept
{
return;
}
恐怕會讓你有點失望,在vs2017的版本中,這個什麼裏面都沒有做的。搞的我也是摸不着頭腦。
3.總結
這節我們知道了operator delete調用流程,對對象的消失有了更深入的理解。同時知道了析構代理函數存在以及作用。
placement delete作用也是不容我們忽視的。