1. 程式人生 > >malloc vs new && delete vs delete[]

malloc vs new && delete vs delete[]

1.malloc vs new

①malloc分配的記憶體位於堆上,new分配的記憶體位於‘自由儲存區’,自由儲存區是C++中一個抽象的概念,有別於堆。一般的g++編譯器實現的new的呼叫過程如下:new operator->operator new->malloc

平時我們用的都是new operator,例如:int* p = new int,new operator會繼續呼叫malloc(malloc不能被過載),所以一般情況下自由儲存區就是在堆上的,但是operator new可以被過載,所以通過operator new分配的記憶體未必都在堆上,因此free store 和 heap還是有點區別

②malloc返回的指標型別是void*,需要手動強轉為需要的型別,而new不需要如此,new的返回型別是型別安全的

③malloc分配記憶體失敗會返回nullptr,而new則會直接丟擲異常。另外new還可以通過set_new_handler設定分配失敗時執行的邏輯,malloc則沒有提供這樣的使用方式

④在陣列的記憶體分配上有所不同,malloc只管分配多大的位元組,new []可以指定分配的是陣列,並使用delete []釋放

⑤new和delete會呼叫類的constructor和destructor,malloc則不會

注:operator new是C++中的函式,內部呼叫malloc,返回型別為void*,只負責分配記憶體,而new operator是在C++編譯層由編譯器實現的,首先呼叫operator new分配記憶體,然後呼叫placement new呼叫物件的構造,如下可能是其實現的簡單版本:

new T;
template<T>
T* new_operator(args) {
   void* mem = ::operator new(sizeof(T));
   T* obj;
   if(BasicTypeTraits<T>::IsBasicType == false){
       obj = new (mem) T(args); //placement new  
   } else {
       obj = mem;
   }
   return obj;
}

其中args是類的建構函式的引數,可以不傳,BaiscTypeTraits用來判斷是否是基本型別,即C++ traits的應用,如果是基本型別無需呼叫建構函式,否則就要使用placement new呼叫物件的建構函式。

這是單個類的情況,如果是new[] 就再加一個迴圈就OK了。

delete  operator與之類似,首先呼叫物件的析構:obj->~T();然後再呼叫operator delete:operator delete(mem);

注:malloc分配的時候要指定分配記憶體的大小,利用free釋放的時候卻不需要指定大小,那麼free是怎麼知道該釋放多大記憶體呢?在實現上,malloc分配的記憶體大小會比傳入的值稍大,多餘的記憶體用來儲存分配的記憶體大小(使用者是無權訪問的,應該是由作業系統管理),然後在free的時候找到這個記憶體的大小,就可以釋放對應的記憶體了。另外因為delete內部實現也是呼叫free,因此delete也不用傳入記憶體大小。

 

2.delete vs delete[]

在使用new/delete的時候,使用new分配記憶體要用delete釋放,使用new[]分配要使用delete[]。那麼二者的區別到底是什麼呢?

delete的過程是:先呼叫物件的解構函式,然後再呼叫free釋放這個物件,delete[]同理。

delete[]的出現主要是為了解決釋放物件陣列的時候呼叫其析構的問題。在使用new A[100]的時候顯示的傳入了建立物件的個數,因此很明確要呼叫多少次建構函式。但是在釋放的時候delete[]還是沒有傳入物件的個數,那麼其該如何知道呼叫多少次解構函式呢?

可能的方式是這樣的:因為delete在內部會呼叫free,而free能拿到記憶體的大小,delete的引數是個指標,可以獲取指標的型別,就可以獲得這個型別的大小,用總大小除以這個型別的大小就可以拿到陣列中物件的個數。但是這樣的前提是free先呼叫,在呼叫free之前不能拿到記憶體大小,而free一旦呼叫整個陣列的記憶體就被釋放了,這時候還沒呼叫物件的析構呢,就可能導致記憶體洩漏了。那delete[]到底是怎麼處理的呢?

答案就是:在new A[100]的時候,這個陣列長度100被存在了整個陣列的前面size_t位元組處(size_t根據平臺不同可能是4或8),相當於new A[100]的時候分配的總記憶體是:malloc需要記錄的記憶體總大小數+物件個數+物件陣列,然後返回地址是物件陣列的首地址。在delete[]的時候先去拿到這個物件個數,再迴圈呼叫解構函式。前面說了malloc記錄那個記憶體總大小使用者無權訪問,但是這個物件個數我們是可以拿到的,示例程式碼如下:

#include<iostream>
using namespace std;

class A{
    public:
    A(){}
    ~A(){}
    int a;
    char b;
};

int main(){

A* a = new A[6];
size_t* p = (size_t*)a;
p--;
cout << *(size_t*)p <<endl;
delete[] a;   //如果換成 delete a;程式可能崩潰
a = NULL;

int* aa = new int[100];
delete aa; 
return 0;  
}

最終的輸出就是6

如果delete[] a換成delete a,那麼就只會呼叫一個物件的析構,然後就將陣列的記憶體釋放掉了,相當於其他5個物件的析構均沒有呼叫,C++對這種情況的處理是未定義行為,運氣好的話最多就是記憶體洩漏,運氣不好程式就崩潰了,所以new[] delete[]必須成對的使用,而不能竄用。

另外,如果new[]的是一個基本型別的陣列,比如int,那麼就不會將其長度記錄,因為基本型別沒有解構函式,直接呼叫free釋放即可,所以上面示例程式的最後delete aa,是可以成功釋放的,但是為了良好的程式設計習慣還是要寫delete []。

 

參考:

https://stackoverflow.com/questions/240212/what-is-the-difference-between-new-delete-and-malloc-free

https://stackoverflow.com/questions/1518711/how-does-free-know-how-much-to-free

https://stackoverflow.com/questions/1350819/c-free-store-vs-heap