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只釋放內存

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM