本文介紹分析一種屢次delete動態內存的狀況。說是典型,是由於這個問題已經在我兩個同事身上發生過;說是隱祕,是由於一旦發生問題,靠肉眼很難肯定緣由。linux
不一樣於C語言經過malloc
和free
等函數實現動態內存的分配和釋放,C++引入了new
和delete
運算符實現。基本的用法以下:函數
int* p = new int; delete p;
若是上述代碼的p
屢次釋放會出現什麼狀況呢?性能
int* p = new int; delete p; delete p;
很明顯會引發程序崩潰,這是我本地執行的錯誤信息,錯誤提示也給出了double free
的字樣,告訴咱們這多是兩次釋放致使的問題。spa
double free or corruption (fasttop): 0x0000000001029c20 *
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x70bfb)[0x7f6469dbcbfb]
/lib/x86_64-linux-gnu/libc.so.6(+0x76fc6)[0x7f6469dc2fc6]
/lib/x86_64-linux-gnu/libc.so.6(+0x7780e)[0x7f6469dc380e]
...
這種狀況有沒有簡單的規避方式嗎?我看到好多人這麼寫:指針
int* p = new int; delete p; p = nullptr; //或 p = NULL; delete p;
因爲C++中delete
空指針是不出錯的,因此執行不會出錯。code
基於工程上的考慮,delete
一個指針後,要把指針賦值爲空指針,藉此提升代碼健壯性。但這每每會掩蓋問題,使得問題查找更難,好比上述問題,咱們應該去分析爲何兩次delete
,而不是經過p = nullptr;
暫時掩蓋問題。對象
上面狀況更好的解決方式是使用智能指針或RAII
,儘可能在高層代碼裏不混雜底層邏輯。在此不絮叨了,進入下一部分。ip
將問題場景簡化爲如下的例子:內存
class List { public: List() { count = 0; elements = nullptr; } //空列表 List(int num) : count(num), elements(new int[num]) {} //num個元素的列表 ~List() { count = 0; delete [] elements; } private: int *elements;//元素的首地址 int count; //元素的個數 }; void processList(List ls) { } int main() { List list(5); processList(list); }
問題的根源出在C++默認的拷貝是「淺拷貝」,即只拷貝當前變量類型所佔用的內存。下面按代碼執行步驟分析:
(1)List list(5);
創建一個對象,該對象{count=5, elements=x}
(2)傳入processList
因爲是淺拷貝,此時ls
變量的值爲{count=5, elements=x}
,即與main
中的list
共用elements
。
(3)processList
函數體結束
因爲ls
對象即將超出做用域,編譯器會調用ls
的析構函數,此時ls
變量的值爲{count=0, elements=無效地址x}
,因爲list
與ls
共享elements
,因此main中的elements
變量的值爲{count=5, elements=無效地址x}
。
(4)main
函數體結束list
也即將超出做用域,編譯器調用list
的析構函數,因爲其elements
已經被delete
了,再釋放一次會出現內存錯誤,致使程序終止。element
那怎麼修正這個問題呢?只要將processList
的ls
改成引用傳參便可,引用傳參僅將使用權
傳給函數,全部權還屬於原對象,因此不會執行析構函數。
void processList(List& ls) { }
固然,還有一種方式是改寫重載賦值和拷貝構造函數,實現「深拷貝」,這樣也能解決問題。標準庫中的string
和vector
是較經常使用的類,因此自己實現了深拷貝,因此不會出現以上問題。在包含的元素較多時,須要考慮性能問題。
C++的零成本抽象,帶來性能優點的同時,必定也要細細考慮其帶來的複雜成本。好比,GC 用爽的人極可能即會犯以上的錯誤,每每不是由於不知道,而是由於相關意識不強烈。因此,必定要當心,當心,再當心。
請關注個人公衆號哦。