深入理解C++ new/delete, new []/delete[]動態內存管理

轉載:https://www.cnblogs.com/tp-16b/p/8684298.html

深入理解C++ new/delete, new []/delete[]動態內存管理

閱讀目錄

在C語言中,我們寫程序時,總是會有動態開闢內存的需求,每到這個時候我們就會想到用malloc/free 去從堆裏面動態申請出來一段內存給我們用。但對這一塊申請出來的內存,往往還需要我們對它進行稍許的“加工”後即初始化 才能爲我們所用,雖然C語言爲我們提供了calloc來開闢一段初始化好(0)的一段內存,但面對象中各是各樣的數據成員初始化,它同樣束手無策。同時,爲了保持良好的編程習慣,我們也都應該對申請出來的內存作手動進行初始化。

對此,這常常讓我們感到一絲繁瑣,於是到了C++中就有了new/delete, new []/delete[] 。用它們便可實現動態的內存管理。

 

點擊回頂部

new/delete, new []/delete [] 基本格式


 new/delete動態管理對象,new[]/delete[]動態管理對象數組。

 

 在C++中,把int 、char..等內置類型的變量也看作對象,它們也是存在構造函數和析構函數的,只是通常對它們,系統調用了默認的構造函數來初始化以及默認的析構(編譯器優化)。所以new int、new int(3)看起來和普通的定義好像沒什麼區別。 但對於自定義類型的對象,此種方式在創建對象的同時,還會將對象初始化好;於是new/delete、new []/delete []方式管理內存相對於malloc/free的方式管理的優勢就體現出來了,因爲它們能保證對象一被創建出來便被初始化,出了作用域便被自動清理。

 

 

點擊回頂部

malloc/free和new/delete的區別和聯繫


   

  *  malloc/free只是動態分配內存空間/釋放空間。而new/delete除了分配空間還會調用構造函數和析構函數進行初始化與清理(清理成員)。

  *  它們都是動態管理內存的入口。
  *  malloc/free是C/C++標準庫的函數,new/delete是C++操作符
  *  malloc/free需要手動計算類型大小且返回值w爲void*,new/delete可自動計算類型的大小,返回對應類型的指針。

  *  malloc/free管理內存失敗會返回0,new/delete等的方式管理內存失敗會拋出異常。

 

儘管看起來new、new[] 和malloc 都能開得空間出來,並且以new 、new[]的方式好像還更有優勢。但從系統層面看來,真正開出空間來的還是malloc。爲什麼這麼說呢?

在C++ Primer書中有提到說: new/delete的表達式與標準庫函數同名了,所以系統並沒有重載new或delete表達式。new/delete真正的實現其實是依賴下面這幾個內存管理接口的。c++中稱之爲“placement版”內存管理接口

接口原型:

void * operator new (size_t size);  
void operator delete (size_t size);

void * operator new [](size_t size);  
void operator delete[] (size_t size);

 

探究它,不妨從這樣一個類AA開始

 類AA

用AA* pA = new AA[10]創建對象,VS下通過調試進入new表達式內部系統函數,得到下面兩個圖:

   和

 

 

 通過上面兩個圖,大致可以看出來new表達式並不直接開闢內存出來,而是通過調用operator new來獲得的內存,而operator new獲得的內存實質上還是用malloc開闢出來的。這便證實了前面所述的:開空間出來還是得 malloc來。

同樣的道理,delete表達式也不是直接去釋放掉內存。比如對上面的對象數組進行delete

AA* pA = new AA[10];
delete[] pa;

 

delete[]實際做了這樣幾件事情:

  * 依次調用pA指向對象數組中每個對象的析構函數,共10次

  * 調用operator delete[](),它將再調用operator delete

  * 底層用free執行operator delete表達式,依次釋放內存

 

 綜合相關資料,小結一下operator new/ operator delete:

   1.operator new/operator delete operator new[]/operator delete[] 和 malloc/free用法一樣。
   2. 他們只負責分配空間/釋放空間,不會調用對象構造函數/析構函數來初始化/清理對象。
   3. 實際operator new和operator delete只是malloc和free的一層封裝

 

如果仔細看過上面的圖,可能會有疑惑:new最後將開闢好內存用指針p返回,pA接收它。可爲什麼p 和pA 會差上4字節?

這其實是因爲編譯器用相差的這4個字節用來保存一個東西——對象個數,即AA* p = new AA[10] 中的‘10’。這也就不難解釋 爲什麼在delete[] 的時候,不用傳給它對象個數。

                               

delete[] 刪除時,將new[] 返回的地址再往前移4個字節便可以拿到要析構的對象個數了。

但是注意:new type[] ,只有type顯示定義析構函數時,編譯器纔會多開4字節來保存對象個數。所以像new int、char這樣的內置類型編譯器不會多開這4字節,編譯器自行優化。

它們之間可用下面的圖展示:

 

 

點擊回頂部

new/delete, new []/delete[], malloc/free配套使用!


 

我們new 出來多少個對象,就得調用多少次析構來對它們進行清理。在用new/delete,new[]/delete[], malloc/free進行內存的管理時,一定不能將它們搞混淆,使用它們一定記得配套使用。

 來看幾個例子,還是以前面AA類爲例

 類AA

1.malloc/delete的組合

複製代碼

void Test1()
{

    AA* p1 = (AA*)malloc(sizeof(AA));   //沒有報錯,但不建議採用,容易引起混淆
    delete p1;                       
    AA* p2 = (AA*)malloc(sizeof(AA));   //報錯,同上,釋放位置也不對
    delete[] p2;
}

複製代碼

2.delete, delete[] 之間誤用(值得注意)

複製代碼

void Test2()
{
    AA* p3 = new AA;         //不報錯,但未清理乾淨。p3的構造函數開闢的空間沒有被釋放
    free(p3);
    AA* p4 = new AA[10];   //崩潰卡死,存在問題,釋放位置被後移了4字節。同時只調用了一次析構函數
   delete p4; ,
   AA* p5 = new AA;     //報錯 非法訪問內存
   delete[] p5; 
}

 ①delete p4錯誤在於釋放位置不對(和編譯器實現new []的機制有關),導致內存泄漏

  
②delete[] p5 直接就崩了,這次new AA的時候並未多開4字節保存對象個數,編譯器便無法知道要調用多少次析構函數(這裏僅僅調用一次析構函數就好了)但編譯器內部還是試圖去訪問p5前4字節的內存,以此獲得對象個數;這便非法內存訪問了,所以程序就掛了。

 

複製代碼

3.針對內置類型

複製代碼

void Test3()
{
    int* p6 = new int[10];  //沒問題
    delete[] p6;
    int* p7 = new int[10];  //沒問題
    delete p7;
    int* p8 = new int[10];  //沒問題
    free(p8);
           
}

複製代碼

 

內存管理內置類型,它們的析構函數其實上是可調可不調的,所以它的實現機制不像前面的new []/delete[],編譯器會自行對處理的數據做記錄,然後處理;所以即便是不匹配的使用,它們也沒出現什麼問題。不僅僅這種內置類型如此那種無自定義類型析構函數的類對象,這樣的用法同樣不會表現出什麼問題。但即便如此,爲保存良好的編程習慣,還是要配對地使用它們!

  結合前面new/delete 的實現機制,便不難分析得出它們若未配對使用可能出現的情況。

 

 

總的來說,記住一點即可:new/delete、new[]/delete[] 配套使用總是沒錯的!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章