天天看點

如何避免記憶體洩漏

對于任何使用C語言的人,如果問他們C語言的最大煩惱是什麼,其中許多人可能會回答說是指針和記憶體洩漏。這些的确是消耗了開發人員大多數調試時間的事項。指針和記憶體洩漏對某些開發人員來說似乎令人畏懼,但是一旦您了解了指針及其關聯記憶體操作的基礎,它們就是您在C語言中擁有的最強大工具。

本文将與您分享開發人員在開始使用指針來程式設計前應該知道的秘密。本文内容包括:

1.導緻記憶體破壞的指針操作類型

2.在使用動态記憶體配置設定時必須考慮的檢查點

3.導緻記憶體洩漏的場景

一、什麼地方可能出錯?

如果您預先知道什麼地方可能出錯,那麼您就能夠小心避免陷阱,并消除大多數與指針和記憶體相關的問題。

有幾種問題場景可能會出現,進而可能在完成生成後導緻問題。在處理指針時,您可以使用本文中的資訊來避免許多問題。

1、未初始化的記憶體

在本例中,p已被配置設定了10個位元組。這10個位元組可能包含垃圾資料,如圖1所示:

char*p=malloc(10);

如何避免記憶體洩漏

圖1.垃圾資料

如果在對這個p指派前,某個代碼段嘗試通路它,則可能會獲得垃圾值,您的程式可能具有不可預測的行為。p可能具有您的程式從未曾預料到的值。

良好的習慣是始終結合使用memset和malloc配置設定記憶體,或者使用calloc。

char*p=malloc(10);
memset(p,’\0’,10);      

現在,即使同一個代碼段嘗試在對p指派前通路它,該代碼段也能正确處理Null值(在理想情況下應具有的值),然後将具有正确的行為。

2、記憶體覆寫

由于p已被配置設定了10個位元組,如果某個代碼片段嘗試向p寫入一個11位元組的值,則該操作将在不告訴您的情況下自動從其他某個位置“吃掉”一個位元組。讓我們假設指針q表示該記憶體。

如何避免記憶體洩漏

圖2.原始q内容

如何避免記憶體洩漏

圖3.覆寫後的q内容

結果,指針q将具有從未預料到的内容。即使您的子產品編碼得足夠好,也可能由于某個共存子產品執行某些記憶體操作而具有不正确的行為。下面的示例代碼片段也可以說明這種場景。

char*name=(char*)malloc(11); //Assignsomevaluetoname
memcpy(p,name,11);//Problembeginshere      

在本例中,memcpy操作嘗試将11個位元組寫到p,而後者僅被配置設定了10個位元組。

作為良好的實踐,每當向指針寫入值時,都要確定對可用位元組數和所寫入的位元組數進行交叉核對。一般情況下,memncpy函數将是用于此目的的檢查點。

3、記憶體讀取越界

記憶體讀取越界(overread)是指所讀取的位元組數多于它們應有的位元組數。這個問題并不太嚴重,在此就不再詳述了。下面的代碼提供了一個示例。

char*ptr=(char*)malloc(10);
charname[20];
memcpy(name,ptr,20);//Problembeginshere      

在本例中,memcpy操作嘗試從ptr讀取20個位元組,但是後者僅被配置設定了10個位元組。這還會導緻不希望的輸出。

4、記憶體洩漏

記憶體洩漏可能真正令人讨厭。下面的清單描述了一些導緻記憶體洩漏的場景。

(1).重新指派

我将使用一個示例來說明重新指派問題。

char*memoryArea=malloc(10);
char*newArea=malloc(10);      

這向如下面的圖4所示的記憶體位置指派。

如何避免記憶體洩漏

圖4.記憶體位置

memoryArea和newArea分别被配置設定了10個位元組,它們各自的内容如圖4所示。如果某人執行如下所示的語句(指針重新指派)……

memoryArea=newArea;      

則它肯定會在該子產品開發的後續階段給您帶來麻煩。

在上面的代碼語句中,開發人員将newArea指針指派給memoryArea指針。結果,memoryArea以前所指向的記憶體位置變成了孤立的,如下面的圖5所示。它無法釋放,因為沒有指向該位置的引用。這會導緻10個位元組的記憶體洩漏。

如何避免記憶體洩漏

圖5.記憶體洩漏

在對指針指派前,請確定記憶體位置不會變為孤立的。

(2).首先釋放父塊

假設有一個指針memoryArea,它指向一個10位元組的記憶體位置。該記憶體位置的第三個位元組又指向某個動态配置設定的10位元組的記憶體位置,如圖6所示。

如何避免記憶體洩漏

圖6.動态配置設定的記憶體

free(memoryArea)      

如果通過調用free來釋放了memoryArea,則newArea指針也會是以而變得無效。newArea以前所指向的記憶體位置無法釋放,因為已經沒有指向該位置的指針。換句話說,newArea所指向的記憶體位置變為了孤立的,進而導緻了記憶體洩漏。

每當釋放結構化的元素,而該元素又包含指向動态配置設定的記憶體位置的指針時,應首先周遊子記憶體位置(在此例中為newArea),并從那裡開始釋放,然後再周遊回父節點。

這裡的正确實作應該為:

free(memoryArea->newArea);
free(memoryArea);      

(3).傳回值的不正确處理

有時,某些函數會傳回對動态配置設定的記憶體的引用。跟蹤該記憶體位置并正确地處理它就成為了calling函數的職責。

char*func()
{
returnmalloc(20);//makesuretomemsetthislocationto‘\0’…
}

voidcallingFunc()
{
func();//Problemlieshere
}      

在上面的示例中,callingFunc()函數中對func()函數的調用未處理該記憶體位置的傳回位址。結果,func()函數所配置設定的20個位元組的塊就丢失了,并導緻了記憶體洩漏。

5、歸還您所獲得的

在開發元件時,可能存在大量的動态記憶體配置設定。您可能會忘了跟蹤所有指針(指向這些記憶體位置),并且某些記憶體段沒有釋放,還保持配置設定給該程式。

始終要跟蹤所有記憶體配置設定,并在任何适當的時候釋放它們。事實上,可以開發某種機制來跟蹤這些配置設定,比如在連結清單節點本身中保留一個計數器(但您還必須考慮該機制的額外開銷)。

6、通路空指針

總結