C++ new 和 delete

C++New和Delete

new和delete

  • 使用new创建对象,delete销毁对象
    使用new创建一个动态类对象时,要执行三个步骤:
    a)调用名为operator new的标准库函数,分配足够大的内存。
    b)调用该类的一个构造函数,创建对象
    c)返回执向该对象的指针
    使用delete删除时,要执行两个步骤:
    a)如果类对象有析构函数则调用析构
    b)释放类对象所占堆空间

    以下面CTest类为例,在VC++编译器中观察其行为:

    class CTest
    {
      int m_nTest;
    public:
      CTest()
      {
        m_nTest = 1;
      }
    
      ~CTest()
      {
        m_nTest = 0;
      }
    };
    

    测试代码如下:

    int main(int argc, char* argv[])
    {
      CTest * p = new CTest;
      return 0;
    }
    

    观察其反汇编:

      CTest * p = new CTest;
      003A1ACD  push        4  
      003A1ACF  call        operator new (03A138Eh)     
      003A1AD4  add         esp,4  
      003A1AD7  mov         dword ptr [ebp-0ECh],eax  
      003A1ADD  mov         dword ptr [ebp-4],0  
      003A1AE4  cmp         dword ptr [ebp-0ECh],0      
      003A1AEB  je          main+70h (03A1B00h)  
      003A1AED  mov         ecx,dword ptr [ebp-0ECh]  
      003A1AF3  call        CTest::CTest (03A10E1h)     
      003A1AF8  mov         dword ptr [ebp-0F4h],eax  
      003A1AFE  jmp         main+7Ah (03A1B0Ah)  
      003A1B00  mov         dword ptr [ebp-0F4h],0  
      003A1B0A  mov         eax,dword ptr [ebp-0F4h]  
      003A1B10  mov         dword ptr [ebp-0E0h],eax  
      003A1B16  mov         dword ptr [ebp-4],0FFFFFFFFh  
      003A1B1D  mov         ecx,dword ptr [ebp-0E0h]  
      003A1B23  mov         dword ptr [p],ecx           
            CTest * p = new CTest;
      00BF1B9D  push        4  
      00BF1B9F  call        operator new (0BF1398h)     //调用new运算符,从堆中分配4字节内存
      00BF1BA4  add         esp,4  
      00BF1BA7  mov         dword ptr [ebp-0ECh],eax  
      00BF1BAD  mov         dword ptr [ebp-4],0  
      00BF1BB4  cmp         dword ptr [ebp-0ECh],0      //检查内存是否分配成功,如果不成功则跳过构造函数的调用
      00BF1BBB  je          main+70h (0BF1BD0h)         
      00BF1BBD  mov         ecx,dword ptr [ebp-0ECh]    //传递this指针
      00BF1BC3  call        CTest::CTest (0BF10E1h)     //内存分配成功后则调用CTest类的构造函数
      00BF1BC8  mov         dword ptr [ebp-10Ch],eax  
      00BF1BCE  jmp         main+7Ah (0BF1BDAh)  
      00BF1BD0  mov         dword ptr [ebp-10Ch],0  
      00BF1BDA  mov         eax,dword ptr [ebp-10Ch]  
      00BF1BE0  mov         dword ptr [ebp-0E0h],eax  
      00BF1BE6  mov         dword ptr [ebp-4],0FFFFFFFFh  
      00BF1BED  mov         ecx,dword ptr [ebp-0E0h]  
      00BF1BF3  mov         dword ptr [p],ecx          //将构造完成后的指针赋值给p
        delete p;
      00BF1BF6  mov         eax,dword ptr [p]  
      00BF1BF9  mov         dword ptr [ebp-104h],eax  
      00BF1BFF  mov         ecx,dword ptr [ebp-104h]  
      00BF1C05  mov         dword ptr [ebp-0F8h],ecx  
      00BF1C0B  cmp         dword ptr [ebp-0F8h],0  
      00BF1C12  je          main+0C9h (0BF1C29h)  
      00BF1C14  push        1                         //析构函数标记,多重继承时使用
      00BF1C16  mov         ecx,dword ptr [ebp-0F8h]  //传递this指针
      00BF1C1C  call        CTest::`scalar deleting destructor' (0BF1276h)    //调用析构函数代理
      00BF1C21  mov         dword ptr [ebp-10Ch],eax  
      00BF1C27  jmp         main+0D3h (0BF1C33h)  
      00BF1C29  mov         dword ptr [ebp-10Ch],0  
    

    析构代理如下:

      CTest::`scalar deleting destructor':
      00BF1A90  push        ebp  
      00BF1A91  mov         ebp,esp  
      00BF1A93  sub         esp,0CCh  
      00BF1A99  push        ebx  
      00BF1A9A  push        esi  
      00BF1A9B  push        edi  
      00BF1A9C  push        ecx  
      00BF1A9D  lea         edi,[ebp-0CCh]  
      00BF1AA3  mov         ecx,33h  
      00BF1AA8  mov         eax,0CCCCCCCCh  
      00BF1AAD  rep stos    dword ptr es:[edi]  
      00BF1AAF  pop         ecx                      //还原this指针
      00BF1AB0  mov         dword ptr [this],ecx  
      00BF1AB3  mov         ecx,dword ptr [this]  
      00BF1AB6  call        CTest::~CTest (0BF123Fh)    //调用类对象的析构函数
      00BF1ABB  mov         eax,dword ptr [ebp+8]  
      00BF1ABE  and         eax,1                       //标记,多重继承时使用
      00BF1AC1  je          CTest::`scalar deleting destructor'+41h (0BF1AD1h)  
      00BF1AC3  push        4                         //传入对象大小
      00BF1AC5  mov         eax,dword ptr [this]      
      00BF1AC8  push        eax                       //传入对象地址
      00BF1AC9  call        operator delete (0BF1069h)   //调用delete运算符释放new运算符分配的对象
      00BF1ACE  add         esp,8  
      00BF1AD1  mov         eax,dword ptr [this]  
      00BF1AD4  pop         edi  
      00BF1AD5  pop         esi  
      00BF1AD6  pop         ebx  
      00BF1AD7  add         esp,0CCh  
      00BF1ADD  cmp         ebp,esp  
      00BF1ADF  call        __RTC_CheckEsp (0BF11E5h)  
      00BF1AE4  mov         esp,ebp  
      00BF1AE6  pop         ebp  
    

    可以看出VC++编译器,为了实现C++标准中new和delete的行为,偷偷插入了不少代码

     
    使用new Type[]动态创建一个类对象的数组,要执行三个步骤:
    a)调用名为operator new[]的标准库函数,分配足够大的内存。
    b)调用该类的默认构造函数,创建数组中的每一个对象
    c)返回对象数组的首地址

    测试代码如下:

    int main(int argc, char* argv[])
    {
      CTest * p = new CTest[10];
      delete[] p;
      return 0;
    }
    

    对应反汇编代码如下:

      CTest * p = new CTest[10];
      00881C6D  push        2Ch  
      00881C6F  call        operator new[] (088143Dh)   //为数组分配空间
      00881C74  add         esp,4  
      00881C77  mov         dword ptr [ebp-0ECh],eax  
      00881C7D  mov         dword ptr [ebp-4],0  
      00881C84  cmp         dword ptr [ebp-0ECh],0      //判断内存是否分配成功,不成功则跳过构造函数 
      00881C8B  je          main+97h (0881CC7h)  
      00881C8D  mov         eax,dword ptr [ebp-0ECh]  
      00881C93  mov         dword ptr [eax],0Ah        //将数组大小存入分配的堆空间前四个字节中
      00881C99  push        offset CTest::~CTest (0881258h)  //传入析构函数的地址作为构造代理函数的参数
      00881C9E  push        offset CTest::CTest (08810E6h)   //传入构造函数的地址作为构造代理函数的参数
      00881CA3  push        0Ah                              //传入数组大小作为构造代理函数的参数
      00881CA5  push        4                                //传入对象的大小作为构造代理函数的参数
      00881CA7  mov         ecx,dword ptr [ebp-0ECh]         //取存放数组的堆地址空间
      00881CAD  add         ecx,4                            //跳过堆空间前4字节,定为到数组中首个对象的地址(见注解1)
      00881CB0  push        ecx                              //数组首地址入栈作为构造代理函数的参数
      00881CB1  call        `eh vector constructor iterator' (088119Ah)  //调用构造代理函数
      00881CB6  mov         edx,dword ptr [ebp-0ECh]       //调整存放数组对象的堆空间指针,使其加4指向数组中首对象的地址
      00881CBC  add         edx,4  
      00881CBF  mov         dword ptr [ebp-10Ch],edx  
      00881CC5  jmp         main+0A1h (0881CD1h)  
      00881CC7  mov         dword ptr [ebp-10Ch],0  
      00881CD1  mov         eax,dword ptr [ebp-10Ch]  
      00881CD7  mov         dword ptr [ebp-0E0h],eax  
      00881CDD  mov         dword ptr [ebp-4],0FFFFFFFFh  
      00881CE4  mov         ecx,dword ptr [ebp-0E0h]  
      00881CEA  mov         dword ptr [p],ecx             //将数组首地址赋值给p
    

    注解1:
    一个CTest对象的大小为4,分配10个对象则总大小为40字节,转成16进制就是0x38,但是从上面的反汇编代码中可以看出
    一共分配了0x3c个字节,多分配了4个字节,VC++编译器用这四个字节来保存所分配数组的大小

    现在来看下构造代理函数的反汇编代码:

      00883960  push        ebp  
      00883961  mov         ebp,esp  
      00883963  push        0FFFFFFFEh  
      00883965  push        88C348h  
      0088396A  push        offset _except_handler4 (0884450h)  
      0088396F  mov         eax,dword ptr fs:[00000000h]  
      00883975  push        eax  
      00883976  add         esp,0FFFFFFECh  
      00883979  push        ebx  
      0088397A  push        esi  
      0088397B  push        edi  
      0088397C  mov         eax,dword ptr [__security_cookie (088D004h)]  
      00883981  xor         dword ptr [ebp-8],eax  
      00883984  xor         eax,ebp  
      00883986  push        eax  
      00883987  lea         eax,[ebp-10h]  
      0088398A  mov         dword ptr fs:[00000000h],eax  
      00883990  mov         dword ptr [i],0        //初始化for循环计数器
      00883997  mov         byte ptr [success],0  
      0088399B  mov         dword ptr [ebp-4],0  
      008839A2  jmp         `eh vector constructor iterator'+4Dh (08839ADh)  
      008839A4  mov         eax,dword ptr [i] 
      /**************************下面代码为for循环主体***************************
      008839A7  add         eax,1  
      008839AA  mov         dword ptr [i],eax  
      008839AD  mov         ecx,dword ptr [i]  
      008839B0  cmp         ecx,dword ptr [count]      //判断是否全部构造完成
      008839B3  je          `eh vector constructor iterator'+74h (08839D4h)  
      008839B5  mov         edx,dword ptr [constructor]  
      008839B8  mov         dword ptr [ebp-24h],edx  
      008839BB  mov         ecx,dword ptr [ebp-24h]  
      008839BE  call        @_guard_check_icall@4 (088144Ch)  
      008839C3  mov         ecx,dword ptr [ptr]      //传递this指针
      008839C6  call        dword ptr [ebp-24h]      //调用构造函数
      008839C9  mov         eax,dword ptr [ptr]      //ptr指向数组中下一个未构造的对象
      008839CC  add         eax,dword ptr [size]  
      008839CF  mov         dword ptr [ptr],eax  
      008839D2  jmp         `eh vector constructor iterator'+44h (08839A4h) //进行下一次循环
      ************************************************************************/
      008839D4  mov         byte ptr [success],1  
      008839D8  mov         dword ptr [ebp-4],0FFFFFFFEh  
      008839DF  call        `eh vector constructor iterator'+86h (08839E6h)  
      008839E4  jmp         $LN12 (0883A04h)  
      $LN11:
      008839E6  movzx       ecx,byte ptr [success]  
      008839EA  test        ecx,ecx  
      008839EC  jne         `eh vector constructor iterator'+0A3h (0883A03h)  
      008839EE  mov         edx,dword ptr [destructor]  
      008839F1  push        edx  
      008839F2  mov         eax,dword ptr [i]  
      008839F5  push        eax  
      008839F6  mov         ecx,dword ptr [size]  
      008839F9  push        ecx  
      008839FA  mov         edx,dword ptr [ptr]  
      008839FD  push        edx  
      008839FE  call        __ArrayUnwind (0881109h)  
      $LN13:
      00883A03  ret  
      $LN12:
      00883A04  mov         ecx,dword ptr [ebp-10h]  
      00883A07  mov         dword ptr fs:[0],ecx  
      00883A0E  pop         ecx  
      00883A0F  pop         edi  
      00883A10  pop         esi  
      00883A11  pop         ebx  
      00883A12  mov         esp,ebp  
      00883A14  pop         ebp  
      00883A15  ret         14h  
    

    再来看看析构:

      delete[] p;
      00881CED  mov         eax,dword ptr [p]  
      00881CF0  mov         dword ptr [ebp-104h],eax  
      00881CF6  mov         ecx,dword ptr [ebp-104h]  
      00881CFC  mov         dword ptr [ebp-0F8h],ecx  
      00881D02  cmp         dword ptr [ebp-0F8h],0     //判断指针p是否为空,不为空则执行析构
      00881D09  je          main+0F0h (0881D20h)  
      00881D0B  push        3   //传入析构标志:1表示析构单个对象,3表示析构数组,0表示仅执行析构不释放对象所占堆空间
      00881D0D  mov         ecx,dword ptr [ebp-0F8h]  //数组首地址通过ecx传递
        delete[] p;
      00881D13  call        CTest::`vector deleting destructor' (088121Ch)  //调用析构代理函数
      00881D18  mov         dword ptr [ebp-10Ch],eax  
      00881D1E  jmp         main+0FAh (0881D2Ah)  
      00881D20  mov         dword ptr [ebp-10Ch],0  
    

    析构代理函数如下:

    CTest::`vector deleting destructor':
    00881AC0  push        ebp  
    00881AC1  mov         ebp,esp  
    00881AC3  push        0FFFFFFFFh  
    00881AC5  push        8884D0h  
    00881ACA  mov         eax,dword ptr fs:[00000000h]  
    00881AD0  push        eax  
    00881AD1  sub         esp,0CCh  
    00881AD7  push        ebx  
    00881AD8  push        esi  
    00881AD9  push        edi  
    00881ADA  push        ecx  
    00881ADB  lea         edi,[ebp-0D8h]  
    00881AE1  mov         ecx,33h  
    00881AE6  mov         eax,0CCCCCCCCh  
    00881AEB  rep stos    dword ptr es:[edi]  
    00881AED  pop         ecx                 //恢复数组首地址到ecx寄存器中
    00881AEE  mov         eax,dword ptr [__security_cookie (088D004h)]  
    00881AF3  xor         eax,ebp  
    00881AF5  push        eax  
    00881AF6  lea         eax,[ebp-0Ch]  
    00881AF9  mov         dword ptr fs:[00000000h],eax  
    00881AFF  mov         dword ptr [this],ecx  
    00881B02  mov         eax,dword ptr [ebp+8]   //取出调用时传入的释放标记参数
    00881B05  and         eax,2  
    00881B08  je          CTest::`vector deleting destructor'+8Eh (0881B4Eh)  
    00881B0A  push        offset CTest::~CTest (0881258h)  //类对象的析构函数地址入栈
    00881B0F  mov         eax,dword ptr [this]    //取数组首地址
    00881B12  mov         ecx,dword ptr [eax-4]   //取数组前面四个字节内容,即数组中的元素个数
    00881B15  push        ecx                     //存放对象数组的堆空间地址入栈(包含存放元素个数的4字节空间)
    00881B16  push        4                       //对象大小入栈
    00881B18  mov         edx,dword ptr [this]  
    00881B1B  push        edx                     //数组首地址入栈
    00881B1C  call        `eh vector destructor iterator' (0881311h)  
    00881B21  mov         eax,dword ptr [ebp+8]  
    00881B24  and         eax,1  
    00881B27  je          CTest::`vector deleting destructor'+86h (0881B46h)  //调用析构函数二次代理
    00881B29  mov         eax,dword ptr [this]  
    00881B2C  mov         ecx,dword ptr [eax-4]  
    00881B2F  lea         edx,[ecx*4+4]  
    00881B36  push        edx  
    00881B37  mov         eax,dword ptr [this]  
    00881B3A  sub         eax,4  
    00881B3D  push        eax  
    00881B3E  call        operator delete[] (088103Ch)  //释放存放数组的整个堆空间
    00881B43  add         esp,8  
    00881B46  mov         eax,dword ptr [this]  
    00881B49  sub         eax,4  
    00881B4C  jmp         CTest::`vector deleting destructor'+0AFh (0881B6Fh)  
    00881B4E  mov         ecx,dword ptr [this]  
    00881B51  call        CTest::~CTest (0881258h)  
    00881B56  mov         eax,dword ptr [ebp+8]  
    00881B59  and         eax,1  
    00881B5C  je          CTest::`vector deleting destructor'+0ACh (0881B6Ch)  
    00881B5E  push        4  
    00881B60  mov         eax,dword ptr [this]  
    00881B63  push        eax  
    00881B64  call        operator delete (088106Eh)  
    00881B69  add         esp,8  
    00881B6C  mov         eax,dword ptr [this]  
    00881B6F  mov         ecx,dword ptr [ebp-0Ch]  
    00881B72  mov         dword ptr fs:[0],ecx  
    00881B79  pop         ecx  
    00881B7A  pop         edi  
    00881B7B  pop         esi  
    00881B7C  pop         ebx  
    00881B7D  add         esp,0D8h  
    00881B83  cmp         ebp,esp  
    00881B85  call        __RTC_CheckEsp (08811F9h)  
    00881B8A  mov         esp,ebp  
    00881B8C  pop         ebp  
    00881B8D  ret         4  
    

    析构函数二次代理:

    00883A80  push        ebp  
    00883A81  mov         ebp,esp  
    00883A83  push        0FFFFFFFEh  
    00883A85  push        88C368h  
    00883A8A  push        offset _except_handler4 (0884450h)  
    00883A8F  mov         eax,dword ptr fs:[00000000h]  
    00883A95  push        eax  
    00883A96  add         esp,0FFFFFFECh  
    00883A99  push        ebx  
    00883A9A  push        esi  
    00883A9B  push        edi  
    00883A9C  mov         eax,dword ptr [__security_cookie (088D004h)]  
    00883AA1  xor         dword ptr [ebp-8],eax  
    00883AA4  xor         eax,ebp  
    00883AA6  push        eax  
    00883AA7  lea         eax,[ebp-10h]  
    00883AAA  mov         dword ptr fs:[00000000h],eax  
    
    00883AB0  mov         byte ptr [success],0   //使得ptr指向对象数组的尾部
    00883AB4  mov         eax,dword ptr [size]  
    00883AB7  imul        eax,dword ptr [count]  
    00883ABB  add         eax,dword ptr [ptr]    
    00883ABE  mov         dword ptr [ptr],eax  
    00883AC1  mov         dword ptr [ebp-4],0  
    
    /********下面代码为for循环主体代码,从数组中最后一个对象开始逐一为其调用析构函数********/
    00883AC8  mov         ecx,dword ptr [count]  
    00883ACB  mov         dword ptr [ebp-24h],ecx  
    00883ACE  mov         edx,dword ptr [count]  
    00883AD1  sub         edx,1  
    00883AD4  mov         dword ptr [count],edx  
    00883AD7  cmp         dword ptr [ebp-24h],0  
    00883ADB  jbe         `eh vector destructor iterator'+7Ch (0883AFCh)  
    00883ADD  mov         eax,dword ptr [ptr]  
    00883AE0  sub         eax,dword ptr [size]  
    00883AE3  mov         dword ptr [ptr],eax  
    00883AE6  mov         ecx,dword ptr [destructor]  
    00883AE9  mov         dword ptr [ebp-20h],ecx  
    00883AEC  mov         ecx,dword ptr [ebp-20h]  
    00883AEF  call        @_guard_check_icall@4 (088144Ch)  
    00883AF4  mov         ecx,dword ptr [ptr]  
    00883AF7  call        dword ptr [ebp-20h]   //调用析构函数
    00883AFA  jmp         `eh vector destructor iterator'+48h (0883AC8h)  
    /********************************************************************/
    00883AFC  mov         byte ptr [success],1  
    00883B00  mov         dword ptr [ebp-4],0FFFFFFFEh  
    00883B07  call        `eh vector destructor iterator'+8Eh (0883B0Eh)  
    00883B0C  jmp         $LN11 (0883B2Ch)  
    $LN10:
    00883B0E  movzx       edx,byte ptr [success]  
    00883B12  test        edx,edx  
    00883B14  jne         `eh vector destructor iterator'+0ABh (0883B2Bh)  
    00883B16  mov         eax,dword ptr [destructor]  
    00883B19  push        eax  
    00883B1A  mov         ecx,dword ptr [count]  
    00883B1D  push        ecx  
    00883B1E  mov         edx,dword ptr [size]  
    00883B21  push        edx  
    00883B22  mov         eax,dword ptr [ptr]  
    00883B25  push        eax  
    00883B26  call        __ArrayUnwind (0881109h)  
    $LN12:
    00883B2B  ret  
    $LN11:
    00883B2C  mov         ecx,dword ptr [ebp-10h]  
    00883B2F  mov         dword ptr fs:[0],ecx  
    00883B36  pop         ecx  
    00883B37  pop         edi  
    00883B38  pop         esi  
    00883B39  pop         ebx  
    00883B3A  mov         esp,ebp  
    00883B3C  pop         ebp  
    00883B3D  ret         10h  
    

    注意:在VC++中,如果类中有自定义的析构函数,则在返回对象数组前面会用一个四字节空间记录数组大小,
    如果没有定义自己的析构函数则不会有这四个字节,原因应该是这样的,因为如果有自定以的析构函数
    在delete的时候就必须要调用析构函数来析构每一个对象,这样做就必须得知道对象个数,如果没有自
    定义的析构函数,在delete的时候只要释放所占用内存即可,不必调析构。

使用new和delete的注意事项

new和delete配套使用,new []应该和delete[]配套使用,从上面的分析来看如果new出来的单个对象,
使用delete[]释放时会取该对象前4个字节的内容作为对象个数,然后执行对应次数的析构(如果类有析构函数),
然后在释放堆空间,这样做绝对会造成程序异常;如果一个new出来的对象数组使用delete释放,只会析构并释放数组中
首元素对象所占内存,造成内存泄漏和其它资源泄漏。

new和delete与malloc和free的区别

  • new和delete为C++中运算符,其原型如下:
    void operator new[](size_t bytes);
    void operator new(size_t bytes);
    void operator delete(void* _Block);
    void operator delete[](void* _Block);
    new和delete可以重载,而malloc和free只是C语言的库函数,和普通函数一样,不是运算符

  • new不仅分配内存,还触发类对象的构造函数,而malloc只是分配内存
    delete先调用对象的析构函数(如果有析构函数),然后在是否对象所占内存,free只释放内存

原文地址:https://www.cnblogs.com/UnknowCodeMaker/p/11272482.html