1. 程式人生 > 實用技巧 >深入理解C++ new/delete, new []/delete[]動態記憶體管理(轉)

深入理解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[] 配套使用總是沒錯的!