你知道C++中new和delete為什么要匹配使用嗎
前言
關(guān)于 new 和 delete 的使用相信大家并不陌生,可是為什么使用 new 的時候要用 delete,使用 new[] 的時候又要用 delete[]。使用 delete 釋放 new[] 申請的內(nèi)存又會發(fā)生什么?為什么有時不匹配不會報錯,有時又會報錯呢?本文將為大家解決這個疑惑。
string* strArray = new string[1002]; ... delete strArray;
這段代碼看起來似乎沒有問題,既使用了 new,又搭配了 delete。但這代碼的行為是未定義的,最好的情況是有部分數(shù)據(jù)沒有析構(gòu),但更可能的是程序直接崩潰。
new 和 delete 做了什么
當使用 new 動態(tài)生成對象時,有兩件事發(fā)生:
1.調(diào)用全局的 operator new 分配內(nèi)存
2.調(diào)用該對象的構(gòu)造函數(shù),對分配的內(nèi)存進行初始化
內(nèi)置類型的構(gòu)造函數(shù)在這時什么都不做,故編譯器不用調(diào)用
當使用 delete 時,同樣會發(fā)生兩件事:
1.針對這段內(nèi)存會有一個(或多個)析構(gòu)函數(shù)被調(diào)用
2.調(diào)用全局的 operator delete 釋放內(nèi)存
delete 的問題在于:將要釋放的內(nèi)存中有多少個對象?這將決定有多少個析構(gòu)函數(shù)被調(diào)用。
這個問題可以轉(zhuǎn)化為:指針所指向的是單一對象還是對象數(shù)組?如果是對象數(shù)組,那么里面又有多少個對象?因此我們有必要記錄數(shù)組的大小,以便 delete 知道需要調(diào)用多少次析構(gòu)函數(shù)。例如下圖:

當然這只是個例子,不同的編譯器可能會有不同的方案,此處我們假定 n 占用 4 字節(jié)。
內(nèi)置類型
new 和 new []
int* num = new int; int* arr = new int[1002];
因為內(nèi)置類型的默認構(gòu)造函數(shù)并不會做什么處理,所以上述代碼在調(diào)用完 ::operator new() / ::operator new[]() 后,不會調(diào)用構(gòu)造函數(shù)。同樣,因為無需調(diào)用構(gòu)造函數(shù),也不用多開辟一塊空間存儲對象個數(shù)。
delete 和 delete [ ]
int* num = new int; int* arr = new int[1002]; delete num; delete[] arr;
對于內(nèi)置類型的 delete 也是非常簡單,只是單純的調(diào)用 ::operator delete() / ::operator delete[]() 釋放內(nèi)存。內(nèi)置類型沒有開辟存儲對象個數(shù)的空間,也不需要析構(gòu)函數(shù)處理。
自定義類型
new 和 new []
string* str1 = new string; string* str2 = new string[1231];
上述代碼首先會調(diào)用 ::operator new() / ::operator new[]() 申請內(nèi)存,然后再調(diào)用 string 的默認構(gòu)造函數(shù)進行初始化工作。
對于 new:因為只有一個對象,最后直接構(gòu)造該對象即可,不用記錄個數(shù)。
對于 new []:因為最后需要調(diào)用析構(gòu)函數(shù)析構(gòu)所有對象,所以需要記錄對象的個數(shù)(即使是使用 new [] 申請一個對象),于是就在內(nèi)存開頭處 4 字節(jié)記錄對象的個數(shù)(一種可能的方案),然后返回實際開辟的內(nèi)存 + 4 的位置。

delete 和 delete [ ]
string* str1 = new string; string* str2 = new string[1231]; delete str1; delete[] str2;
上述代碼首先會調(diào)用 string 的析構(gòu)函數(shù)進行清理工作,然后再調(diào)用 ::operator delete() / ::operator delete[]() 釋放內(nèi)存。
對于 delete:直接析構(gòu)該位置對象,然后釋放即可。
對于 delete[]:首先會查看前面 4 字節(jié)存儲的個數(shù),來決定調(diào)用析構(gòu)函數(shù)的次數(shù);然后再調(diào)用 ::operator delete[]() 釋放內(nèi)存,實際上 ::operator delete[]() 釋放的是地址減 4 后的值。因為實際開辟的前面還有 4 個字節(jié),要是不修正的話直接釋放的話,程序就會崩潰,因為系統(tǒng)并沒記錄該地址。
問題
自定義類型為什么要匹配使用
1.使用 new 和 delete
開辟一個對象,釋放一個對象,沒有問題
2.使用 new[] 和 delete
開辟一組對象,調(diào)用 ::operator delete(),因為沒有對地址進行修正,釋放了非法地址,程序會崩潰
3.使用 new 和 delete[]
開始一個對象,調(diào)用 ::operator delete[](),此時仍會把前面 4 個字節(jié)當成個數(shù)調(diào)用析構(gòu)函數(shù)(個數(shù)是不確定的),然后再釋放減 4 后的地址,同樣釋放了非法地址,導(dǎo)致程序崩潰
4.使用 new[] 和 delete[]
開辟一組對象,釋放一組對象,沒有問題
內(nèi)置類型不匹配為什么不報錯
使用 new[] 和 delete 或者 使用 new 和 delete[]
因為對于內(nèi)置類型,默認情況下不會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù),也不用開辟空間存儲對象個數(shù)。
因此最后的地址也不需要修正,于是就不會發(fā)生報錯了。
疑惑
其實全局的 new 和 delete 最后調(diào)用的是 malloc 和 free,malloc 在開辟空間時,同樣會多開辟一塊空間用于存儲內(nèi)存塊的信息(在 Linux 64 位環(huán)境下是 16 字節(jié))。

頭信息中有存儲內(nèi)存的字節(jié)大小,所以 free 時可以知道要釋放的內(nèi)存大小。
那么問題來了,如果說對于自定義類型 new[] 返回的地址是上圖 malloc 返回的地址,對于該地址調(diào)用全局 delete,再傳遞給 free 此時是可以正確釋放內(nèi)存的,只是析構(gòu)函數(shù)調(diào)用次數(shù)不正確。但實際上程序直接崩潰,也就是說此時 new[] 返回的地址應(yīng)該是 malloc 返回的地址右移 4 字節(jié)的值。

對這段內(nèi)存模型只是猜測,博主也是菜雞,希望有大佬可以指正一下。
到此這篇關(guān)于你知道C++中new和delete為什么要匹配使用嗎的文章就介紹到這了,更多相關(guān)C++ new delete內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ 中l(wèi)ambda表達式的編譯器實現(xiàn)原理
C++ 11加入了一個非常重要的特性——Lambda表達式。這篇文章主要介紹了C++ 中l(wèi)ambda表達式的編譯器實現(xiàn)原理,需要的朋友可以參考下2017-02-02
Visual Studio 2019修改編碼UTF-8的實現(xiàn)
這篇文章主要介紹了Visual Studio 2019修改編碼UTF-8的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

