C++幕后故事(十)--对象离我们而去

C++幕后故事(十)–对象离我们而去

三国演义里面说过一句话:天下大事,合久必分,分久必合。有相聚,就有分离的时候。今天我们主要聊聊operator delete的故事

今天我们主要学习知识点:

1.delete的调用流程。
2.我们重载了delete之后能干啥。
3.placement delete有啥用。

1. operator delete操作符的原理

1.1 operator delete 调用流程

测试代码如下:

/****************************************************************************
**
** Copyright (C) 2019 635672377@qq.com
** 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作为参数,表示需要释放内存,我在上面也做了注释。

  1. 为什么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


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 635672377@qq.com

文章标题:C++幕后故事(十)--对象离我们而去

文章字数:1.3k

本文作者:刘世雄

发布时间:2020-01-10, 14:11:06

最后更新:2020-01-10, 06:11:56

原始链接:http://lsxcpp.com/2020/01/10/cpp-10-object-distance-us/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录