反汇编理解C++的new和delete

前言

如果你对C++的new和delete操作符还有些疑问的话,那么本文可能是你所需要的。

operator new和operator delete

在讲new和delete前我们先讲operator new和operator delete,operator new和operator delete是C++标准库中的函数,函数原型如下:

    void* operator new(size_t);     // 分配内存
    void operator delete(void*);    // 释放内存

这两个函数和我们普通函数不一样的是函数名中间有空格,我们自己写代码是不能这样做的,至于为什么要设计成这样我也不知道。我们把它们当作普通的函数就可以了,它两和C语言的malloc和free比较类似,我们在写C++代码的时候是可以直接使用这两个函数的,如下:

    int main()
    {
        // 分配1024个字节的内存
        void* pBuffer = operator new(1024);

        // 释放内存
        operator delete(pBuffer);
        pBuffer = nullptr;

        return 0;
    }

第一个示例:new一个内置类型对象

new和delete是C++中的操作符,当我们使用new和delete时编译器帮助我们生成了一些代码,这些隐藏起来的代码可能就是困扰我们的地方。我们可以通过反汇编来探究其中的秘密,先看一个简单的示例:

    int* pValue = new int;

    delete pValue;
    pValue = nullptr;

以上代码在VS中Debug模式下的反汇编为:


    int* pValue = new int;
    00C513AE  push        4 ;参数入栈, 需要分配的字节大小
    00C513B0  call        operator new (0C51177h) ;调用函数, 分配内存
    00C513B5  add         esp,4 ;栈恢复
    00C513B8  mov         dword ptr [ebp-0E0h],eax ;临时变量中存储分配的内存地址
    00C513BE  mov         eax,dword ptr [ebp-0E0h]  
    00C513C4  mov         dword ptr [pValue],eax ;变量pValue存储分配的内存地址

    delete pValue;
    00C513C7  mov         eax,dword ptr [pValue]  
    00C513CA  mov         dword ptr [ebp-0D4h],eax ;临时变量中存储分配的内存地址
    00C513D0  mov         ecx,dword ptr [ebp-0D4h]  
    00C513D6  push        ecx ;参数入栈, 分配的内存地址
    00C513D7  call        operator delete (0C51082h) ;调用函数, 释放内存
    00C513DC  add         esp,4 ;栈恢复
    pValue = nullptr;
    00C513DF  mov         dword ptr [pValue],0  

我们看到使用new时会调用函数operator new,使用delete时会调用函数operator delete,并且new操作符还帮我们计算了需要分配的内存大小。

第二个示例:new一个类类型对象

    string* pStr = new string();
    delete pStr;
    pStr = nullptr;

部分反汇编代码为:


    string* pStr = new string();
    001B15AD  push        20h  
    001B15AF  call        operator new (1B1235h) ;分配内存
    001B15B4  add         esp,4  
    001B15B7  mov         dword ptr [ebp-0F8h],eax  
    001B15BD  mov         dword ptr [ebp-4],0  
    001B15C4  cmp         dword ptr [ebp-0F8h],0  
    001B15CB  je          main+70h (1B15E0h)  
    001B15CD  mov         ecx,dword ptr [ebp-0F8h]  
    001B15D3  call        string::string(1B1037h) ;调用构造函数
    001B15D8  mov         dword ptr [ebp-10Ch],eax  
    001B15DE  jmp         main+7Ah (1B15EAh)  
    001B15E0  mov         dword ptr [ebp-10Ch],0  
    001B15EA  mov         eax,dword ptr [ebp-10Ch]  
    001B15F0  mov         dword ptr [ebp-104h],eax  
    001B15F6  mov         dword ptr [ebp-4],0FFFFFFFFh  
    001B15FD  mov         ecx,dword ptr [ebp-104h]  
    001B1603  mov         dword ptr [ebp-14h],ecx  
    delete pStr;
    001B1606  mov         eax,dword ptr [ebp-14h]  
    001B1609  mov         dword ptr [ebp-0E0h],eax  
    001B160F  mov         ecx,dword ptr [ebp-0E0h]  
    001B1615  mov         dword ptr [ebp-0ECh],ecx  
    001B161B  cmp         dword ptr [ebp-0ECh],0  
    001B1622  je          main+0C9h (1B1639h)  
    001B1624  push        1  
    001B1626  mov         ecx,dword ptr [ebp-0ECh]  
    001B162C  call        string::`scalar deleting destructor' (1B12A8h)  
    001B1631  mov         dword ptr [ebp-10Ch],eax  
    001B1637  jmp         main+0D3h (1B1643h)  
    001B1639  mov         dword ptr [ebp-10Ch],0  
    pStr = nullptr;
    001B1643  mov         dword ptr [ebp-14h],0  


    string::`scalar deleting destructor':
    001B16B0  push        ebp  
    001B16B1  mov         ebp,esp  
    001B16B3  sub         esp,0CCh  
    001B16B9  push        ebx  
    001B16BA  push        esi  
    001B16BB  push        edi  
    001B16BC  push        ecx  
    001B16BD  lea         edi,[ebp-0CCh]  
    001B16C3  mov         ecx,33h  
    001B16C8  mov         eax,0CCCCCCCCh  
    001B16CD  rep stos    dword ptr es:[edi]  
    001B16CF  pop         ecx  
    001B16D0  mov         dword ptr [ebp-8],ecx  
    001B16D3  mov         ecx,dword ptr [this]  
    001B16D6  call        string::~string(1B11C7h) ;调用析构函数
    001B16DB  mov         eax,dword ptr [ebp+8]  
    001B16DE  and         eax,1  
    001B16E1  je          `scalar deleting destructor'+3Fh (1B16EFh)  
    001B16E3  mov         eax,dword ptr [this]  
    001B16E6  push        eax  
    001B16E7  call        operator delete (1B10B9h) ;释放内存
    001B16EC  add         esp,4  
    001B16EF  mov         eax,dword ptr [this]  
    001B16F2  pop         edi  
    001B16F3  pop         esi  
    001B16F4  pop         ebx  
    001B16F5  add         esp,0CCh  
    001B16FB  cmp         ebp,esp  
    001B16FD  call        @ILT+475(__RTC_CheckEsp) (1B11E0h)  
    001B1702  mov         esp,ebp  
    001B1704  pop         ebp  
    001B1705  ret         4  

当使用new操作符创建一个类类型对象时实际上发生了3个步骤:

  1. 调用函数operator new分配内存
  2. 调用构造函数进行构造对象
  3. 返回构造的对象的指针

当使用delete操作符释放一个类类型对象时实际上发生了2个步骤:

  1. 调用析构函数进行析构对象
  2. 调用函数operator delete释放内存

operator new[]和operator delete[]

在C++标准库中也有如下两个函数:

    void* operator new[](size_t);     // 分配内存
    void operator delete[](void*);    // 释放内存

当我们分配数组时我们会使用new []和delete []操作符,new []和delete []操作符分别会调用operator new[]和operator delete[]这两个函数。

第三个示例:new一个内置类型对象数组

    int* pArray = new int[3];

    delete [] pArray;
    pArray = nullptr;

反汇编代码为:


    int* pArray = new int[3];
    012813BE  push        0Ch  
    012813C0  call        operator new[] (12810A0h) ;调用函数分配内存
    012813C5  add         esp,4  
    012813C8  mov         dword ptr [ebp-0E0h],eax  
    012813CE  mov         eax,dword ptr [ebp-0E0h]  
    012813D4  mov         dword ptr [pArray],eax  

    delete [] pArray;
    012813D7  mov         eax,dword ptr [pArray]  
    012813DA  mov         dword ptr [ebp-0D4h],eax  
    012813E0  mov         ecx,dword ptr [ebp-0D4h]  
    012813E6  push        ecx  
    012813E7  call        operator delete[] (128101Eh) ;调用函数释放内存
    012813EC  add         esp,4  
    pArray = nullptr;
    012813EF  mov         dword ptr [pArray],0  

我们可以看到函数operator new[]和operator delete[]都有被调用。

第4个示例:new一个类类型对象数组

    class Test
    {
    public:
        Test()
        {
        }

        ~Test()
        {
        }

    private:
        int m_value1;
        int m_value2;
    };

    int main()
    {
        Test* pArray = new Test[3];

        delete[] pArray;
        pArray = nullptr;

        return 0;
    }

反汇编代码为:


    Test* pArray = new Test[3];
    0104142D  push        1Ch ;注意:这里分配的内存大小为28个字节
    0104142F  call        operator new[] (10410AFh) ;分配内存
    01041434  add         esp,4  
    01041437  mov         dword ptr [ebp-0F8h],eax  
    0104143D  mov         dword ptr [ebp-4],0  
    01041444  cmp         dword ptr [ebp-0F8h],0  
    0104144B  je          main+97h (1041487h)  
    0104144D  mov         eax,dword ptr [ebp-0F8h]  
    01041453  mov         dword ptr [eax],3  
    01041459  push        offset Test::~Test (104105Fh)  
    0104145E  push        offset Test::Test (1041177h)  
    01041463  push        3  
    01041465  push        8  
    01041467  mov         ecx,dword ptr [ebp-0F8h]  
    0104146D  add         ecx,4  
    01041470  push        ecx  
    01041471  call        `eh vector constructor iterator' (1041122h) ;迭代构造
    01041476  mov         edx,dword ptr [ebp-0F8h]  
    0104147C  add         edx,4  
    0104147F  mov         dword ptr [ebp-10Ch],edx  
    01041485  jmp         main+0A1h (1041491h)  
    01041487  mov         dword ptr [ebp-10Ch],0  
    01041491  mov         eax,dword ptr [ebp-10Ch]  
    01041497  mov         dword ptr [ebp-104h],eax  
    0104149D  mov         dword ptr [ebp-4],0FFFFFFFFh  
    010414A4  mov         ecx,dword ptr [ebp-104h]  
    010414AA  mov         dword ptr [ebp-14h],ecx  

    delete[] pArray;
    010414AD  mov         eax,dword ptr [ebp-14h]  
    010414B0  mov         dword ptr [ebp-0E0h],eax  
    010414B6  mov         ecx,dword ptr [ebp-0E0h]  
    010414BC  mov         dword ptr [ebp-0ECh],ecx  
    010414C2  cmp         dword ptr [ebp-0ECh],0  
    010414C9  je          main+0F0h (10414E0h)  
    010414CB  push        3  
    010414CD  mov         ecx,dword ptr [ebp-0ECh]  
    010414D3  call        Test::`vector deleting destructor' (1041041h); 析构
    010414D8  mov         dword ptr [ebp-10Ch],eax  
    010414DE  jmp         main+0FAh (10414EAh)  
    010414E0  mov         dword ptr [ebp-10Ch],0  
    pArray = nullptr;
    010414EA  mov         dword ptr [ebp-14h],0  

从反汇编代码中我们可以看出我们需要分配的总内存大小为28字节,我们的每个Test对象占用8个字节,3个Test对象占用24个字节,为什么要多分配4个字节?其实这多分配出来的4个字节就是用来记录数组中对象的个数的。汇编代码中 eh vector constructor iterator 函数中有对应的构造函数的调用,vector deleting destructor 函数中有对应析构函数和operator delete[]函数的调用,具体汇编内容比较多,我们省略。

当使用new []操作符创建一个类类型对象数组时实际上发生了3个步骤:

  1. 调用函数operator new[]分配内存(比数组总大小多4个字节)
  2. 调用构造函数进行构造对象(构造次数为数组中对象的个数)
  3. 返回构造的数组的指针

当使用delete []操作符释放一个类类型对象数组时实际上发生了2个步骤:

  1. 对数组中每个对象分别进行析构
  2. 调用函数operator delete[]释放内存(包括数组的空间和记录数组大小的4个字节的空间,delete []操作符会正确找到需要释放的内存的地址)

重载new和delete

实际上new、delete、new []、delete[]这4个操作符是没办法重载的,但是我们可以重载它们所对应使用的内存分配函数:

    void* operator new(size_t)
    {
        // 我的实现
    }
    void operator delete(void*)
    {
        // 我的实现
    }

    void* operator new[](size_t)
    {
        // 我的实现
    }

    void operator delete[](void*)
    {
        // 我的实现
    }

类特定的new和delete

如果我们重载上面的4个函数,那么全局的new和delete都会被改变,有时候我们可能不希望这么做而仅仅针对某个类的new和delete。这样的话我们就需要定义类成员的operator new和operator delete函数:

    class Test
    {
    public:
        void* operator new(size_t size)
        {
            return malloc(size);
        }
        void operator delete(void* p)
        {
            free(p);
        }

    public:
        Test()
        {
        }

        ~Test()
        {
        }

    private:
        int m_value1;
        int m_value2;
    };

这样的话我们每次new和delete Test对象时都会调用我们自己的operator new和operator delete函数,而全局的new和delete并不受影响。实际上类中的operator new和operator delete函数隐式地为静态函数,不必显式地将它们声明为static。 类中的operator new[]和operator delete[]函数同样如此。

每当我们new一个对象时,编译器都会去查找我们类中是否定义了operator new和operator delete函数,如果定义了就会使用我们自定义的函数,如果没定义就查找有没有全局的operator new和operator delete函数,如果也没有就会使用标准库中的operator new和operator delete函数。


赞助作者写出更好文章


您还未登录,登录GitHub账号发表精彩评论

 GitHub登录


最新评论

    还没有人评论...

 

 

刘杰

29岁, 现居苏州

微信:

CoderJieLiu

邮箱:

coderjie@outlook.com

Github:

https://github.com/BurnellLiu

简介:

弱小和无知不是生存的障碍,傲慢才是!

Think Twice, Code Once!

本站: 登录 注册

苏ICP备16059872号-1 苏ICP备16059872号-2 . Copyright © 2017. http://www.coderjie.com. All rights reserved.

账号登录

注册 忘记密码