• 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只释放内存

  • 相关阅读:
    [Next] 六.next的优化
    [Next] 五.next自定义内容
    Mac解决端口占用
    [Next] 四.在next中引入redux
    [Next] 服务端渲染知识补充
    [Next] 三.next自定义服务器和路由
    哪些使用UDP、TCP、IP协议
    IDEA配置git
    ssm整合配置
    git传输远程仓库
  • 原文地址:https://www.cnblogs.com/UnknowCodeMaker/p/11272482.html
Copyright © 2020-2023  润新知