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