内存泄漏的原因及解决办法_编程基础 | C++片段 指针、多态和内存分配
本片段將介紹運行期而不是編譯期的內存分配
1.變量的內存分配和方法的前期綁定
函數中聲明的局部變量與其參數以及簿記數據一起被放置在一個活動記錄中。活動記錄存儲在名為運行期棧(run-time stack)的應用程序內存中。
函數調用
運行期棧創建活動記錄 函數結束 銷毀運行期棧,釋放內存當創建對象時,對象數據成員的存儲也在當前執行函數或者方法的活動記錄中。
大多數情況,程序只需要自動內存管理和前期綁定。以下兩種情況前期綁定無法解決:
(1)需要利用多態
(2)需要訪問的對象在創建它的函數或方法之外
2.需要解決的問題
程序會在編譯器判斷調用方法的版本,會與聲明一個對象時的類型相匹配,而不是和之后定義的派生類相匹配,在此需要一種方法告訴編譯器在程序運行的時候判斷需要執行的代碼,這就是所謂的后期綁定,是多態的特征。為解決這個問題,需要兩個工具:指針變量和虛函數。
3.指針和程序的自由存儲
(1)C++內存分配
為利用后期綁定,不想讓對象成為運行期棧上的活動記錄,于是操作系統為代碼設置了內存(稱為代碼存儲或者文本存儲),為全局變量和靜態變量設置了內存(稱為靜態存儲),程序還被給予了額外的內存,稱為自由存儲(free store)或者應用程序堆(application heap),可以在這里存儲數據。
圖1 程序內存布局示例new運算符在自由存儲中分配內存
運行期棧中的變量所擁有的內存是自動分配與釋放的,而自由存儲中的變量即使在創建它們的函數或者方法終止之后都會存在。(容易內存泄漏)
用指針指向對象,e.g.
MagicBox注意:如果聲明了一個指針變量,但是沒有立即創建一個對象供其引用,則應該將這個指針設置為nullptr。如下的賦值是必要的,因為C++不會初始化指針。
ToyBox<int>* myToyPtr=nullptr;(2)釋放內存
當指針變量所指的內存不再需要之后,需要使用delete運算符將其釋放,然后將指針變量的值設置為nullptr,表示這個變量不再引用或者指向任何對象。
delete如果在此示例中沒有將somePtr設置為nullptr,那么somePtr就是應該懸掛指針(dangling pointer),因為這個指針仍然保存被釋放對象的地址,懸掛指針是嚴重錯誤的來源。
(3)避免內存泄漏
①當在自由存儲中創建了對象,但程序無法再訪問這個對象時,就發生了內存泄漏。
MagicBox<string>* myBoxPtr=new MagicBox<string>(); MagicBox<string>* yourBoxPtr=new MagicBox<string>(); yourBoxPtr=myBoxPtr;//Results is inaccessible object圖2 賦值導致對象不可訪問解決辦法:應該將yourBoxPtr初始化為nullptr,或者是用myBoxPtr賦初值。
②當函數或者方法在自由存儲中創建了對象,并且由于沒有將指針你返回給調用者或者沒有將其存儲在類數據成員中而丟失了指向對象的指針時,就會發生更加微妙的內存泄漏。
//不合理的函數在自由存儲中分配內存 void myLeakyFunction(const double& someItem) {ToyBox<double>* someBoxPtr=new ToyBox<double>();someBoxPtr->setItem(someItem); }//end myLeakyFunctionsomeBoxPtr存儲在運行期棧中,函數結束即銷毀,創建的對象存放在自由存儲中,無法獲取其地址而發生內存泄漏。
解決辦法:在函數終止前刪除對象;不要再自由存儲中分配內存,而是使用局部變量;若函數內部創建的對象再外部會用到,則可以返回指向對象的指針,使用指針的代碼段負責刪除對象,注釋中應該注明,但仍有錯誤使用的風險,如直接調用而不賦給其他指針。
ToyBox<double>* pluggedLeakyFunction(const double& someItem) {ToyBox<double>* someBoxPtr=new ToyBox<double>();someBoxPtr->setItem(someItem);return someBoxPtr; }//end pluggedLeakyFunctionpluggedLeakyFunction(boxValue)//Misused;returned pointer is lost為了防止內存泄漏,最好的方法不是使用函數返回新創建對象的指針,而是定義一個類,類中的方法完成這一任務。類負責刪除自由存儲中的對象,確保不會發生內存泄漏。這個類最少有三個部分:在自由存儲中創建對象的方法、指向這個對象的數據字段,以及當類的實例不再需要的時候刪除這個對象的方法,也就是析構函數。
通常,編譯器生成的析構函數對類而言已經足夠,但是如果類本身使用new運算符創建了對象,為了安全起見,實現析構函數時應該確保為對象分配的內存被釋放。
(4)避免懸掛指針
可能導致懸掛指針的情況
①如果在使用delete之后不將指針變量設置為nullptr
②如果聲明了一個指針變量但是不對其賦值
③兩個指針指向同一個對象,刪除了其中一個指針并置空,另一個指針成為懸掛指針
MagicBox<string>* myBoxPtr=new MagicBox<string>(); MagicBox<string>* yourBoxPtr=myBoxPtr;delete myBoxPtr; myBoxPtr=nullptr;//共同指向的對象已不存在 yourBoxPtr->getItem();//無法調用其方法懸掛指針的解決辦法:
- 初始化或者不需要的時候,指針變量設置為nullptr
- 減少別名的使用
- 刪除對象時,將所有引用這個被刪除對象的別名設置為nullptr
4.虛方法和多態
實現多態需要編譯器執行后期綁定,為此必須將基類的方法聲明為virtual。
為了實現后期綁定,必須在自由存儲中創建變量并使用指針指向這些變量。
關于虛方法的要點:
- 虛方法是派生類可以重寫的方法。
- 必須實現類的虛方法(純虛方法不包含在內)。
- 派生類不需要重寫被繼承的虛方法的已有實現。
- 類的任何方法都可以是虛方法。當然,如果不想讓派生類重寫某些特定的方法,那么這些方法就不應該是虛方法。
- 析構函數不能是虛方法。
- 析構函數可以是也應該是虛方法。虛析構函數確保了對象的后代可以正確地釋放自身。
- 虛方法的返回類型不能被重寫。
5.數組的動態分配
int arraySize=50; double* anArray=new double[arraySize]; //可以在程序運行期對arraySize賦值,改變數組的大小delete []anArray;數組大小用完后分配更多空間,并將原有數組復制過來
double* oldArray=anArray; //Copy pointer to array anArray=new double(2*arraySize) //Double array size for(int index=0;index<arraySize;index++) //Copy old arrayanArray[index]=oldArray[index]; delete []oldArray; //Deallocate old array本文參考《C++數據抽象和問題求解》第6版 清華大學出版社 [美]Frank M.Carrano Timothy Henry著 景麗譯
總結
以上是生活随笔為你收集整理的内存泄漏的原因及解决办法_编程基础 | C++片段 指针、多态和内存分配的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 70-200二代和三代区别
- 下一篇: c++ socket线程池原理_Thre