C語言中文網 目錄
首頁 > 編程筆記 > C語言筆記 > 指針 閱讀:2,138

內存泄漏的場景分析和避免方法總結,C語言內存泄漏詳解

大家都知道,在堆上分配的內存,如果不再使用了,就應該及時釋放,以便后面其他地方可以重用。而在 C 語言中,內存管理器不會自動回收不再使用的內存。如果忘了釋放不再使用的內存,這些內存就不能被重用了,這就造成了內存泄漏

內存泄漏幾乎是很難避免的,不管是老手還是新手,都存在這個問題,甚至 Windows 與 Linux 這類系統軟件也或多或少存在著內存泄漏。

也許對一般的應用軟件來說,這個問題似乎不是那么突出與嚴重。一兩處內存泄漏通常并不致于讓程序崩潰,也不會帶來邏輯上的錯誤,而且在進程退出時,系統會自動釋放所有與該進程相關的內存(共享內存除外),所以內存泄漏的后果相對來說還是比較溫和的。但是,量變會導致質變,一旦內存泄漏過多以致耗盡內存,后續內存分配將會失敗,程序就可能因此而崩潰。

在常見情況下,內存泄漏的主要可見癥狀就是罪魁進程的速度減慢。原因是體積大的進程更有可能被系統換出,讓別的進程運行,而且大的進程在換進換出時花費的時間也更多。即使泄漏的內存本身并不被引用,但它仍然可能存在于頁面中(內容自然是垃圾),這樣就增加了進程的工作頁數量,降低了性能。

下面展示了一些導致內存泄漏的常見場景。

1) 指針重新賦值

看下面一段示例代碼:
char * p = (char *)malloc(10);
char * np = (char *)malloc(10);
其中,指針變量 p 和 np 分別被分配了 10 個字節的內存,它們各自的內存如圖 1 所示。


圖 1 p 和 np 賦值前的內存

如果程序需要執行如下賦值語句:
p=np;
這時候,指針變量 p 被 np 指針重新賦值,其結果是 p 以前所指向的內存位置變成了孤立的內存,如圖 2 所示。它無法釋放,因為沒有指向該位置的引用,從而導致 10 字節的內存泄漏。


圖 2 p 和 np 賦值后的內存

因此,在對指針賦值前,一定確保內存位置不會變為孤立的。

2) 錯誤的內存釋放

假設有一個指針變量 p,它指向一個 10 字節的內存位置。該內存位置的第三個字節又指向某個動態分配的 10 字節的內存位置,如圖 3 所示。


圖 3 p 所指向的內存

如果程序需要執行如下賦值語句時:
free(p);
很顯然,如果通過調用 free 來釋放指針 p,則 np 指針也會因此而變得無效。np 以前所指向的內存位置也無法釋放,因為已經沒有指向該位置的指針。換句話說,np 所指向的內存位置變為孤立的,從而導致內存泄漏。

因此,每當釋放結構化的元素,而該元素又包含指向動態分配的內存位置的指針時,應首先遍歷子內存位置(如本示例中的 np),并從那里開始釋放,然后再遍歷回父節點,如下面的代碼所示:
free(p->np);
free(p);

3) 返回值的不正確處理

有時候,某些函數會返回對動態分配的內存的引用,如下面的示例代碼所示:
char *f()
{
    return (char *)malloc(10);
}
void f1()
{
    f();
}
很明顯,函數 f1 中對 f 函數的調用并未處理該內存位置的返回地址,其結果將導致 f 函數所分配的 10 個字節的塊丟失,并導致內存泄漏。

4) 在內存分配后忘記使用 free 進行釋放

最后,要避免這些內存相關的問題導致的內存越界與內存遺漏等錯誤,可以參考如下幾點進行:
  • 確保沒有在訪問空指針。
  • 每個內存分配函數都應該有一個 free 函數與之對應,alloca 函數除外。
  • 每次分配內存之后都應該及時進行初始化,可以結合 memset 函數進行初始化,calloc 函數除外。
  • 每當向指針寫入值時,都要確保對可用字節數和所寫入的字節數進行交叉核對。
  • 在對指針賦值前,一定要確保沒有內存位置會變為孤立的。
  • 每當釋放結構化的元素(而該元素又包含指向動態分配的內存位置的指針)時,都應先遍歷子內存位置并從那里開始釋放,然后再遍歷回父節點。
  • 始終正確處理返回動態分配的內存引用的函數返回值。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo