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