C++幕後故事(十)--對象離我們而去

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
}

根據這彙編代碼我畫出流程圖:

image

看到了這張流程圖,你一定對圖中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. 析構代理函數裏面又做了其他的事,比如是否需要釋放內存,再比如對象數組又是怎麼釋放內存。

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作用也是不容我們忽視的。

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章