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

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

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

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

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

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

1) 指針重新賦值

看下面一段示例代碼:


 
  1. char * p = (char *)malloc(10);
  2. char * np = (char *)malloc(10);

其中,指針變量 p 和 np 分別被分配了 10 個字節的內存,它們各自的內存如圖 1 所示。



圖 1 p 和 np 賦值前的內存


如果程序需要執行如下賦值語句:


 
  1. p=np;

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



圖 2 p 和 np 賦值後的內存


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

2) 錯誤的內存釋放

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



圖 3 p 所指向的內存


如果程序需要執行如下賦值語句時:


 
  1. free(p);

很顯然,如果通過調用 free 來釋放指針 p,則 np 指針也會因此而變得無效。np 以前所指向的內存位置也無法釋放,因爲已經沒有指向該位置的指針。換句話說,np 所指向的內存位置變爲孤立的,從而導致內存泄漏。

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


 
  1. free(p->np);
  2. free(p);

3) 返回值的不正確處理

有時候,某些函數會返回對動態分配的內存的引用,如下面的示例代碼所示:


 
  1. char *f()
  2. {
  3. return (char *)malloc(10);
  4. }
  5. void f1()
  6. {
  7. f();
  8. }

很明顯,函數 f1 中對 f 函數的調用並未處理該內存位置的返回地址,其結果將導致 f 函數所分配的 10 個字節的塊丟失,並導致內存泄漏。

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

最後,要避免這些內存相關的問題導致的內存越界與內存遺漏等錯誤,可以參考如下幾點進行:

  • 確保沒有在訪問空指針。
  • 每個內存分配函數都應該有一個 free 函數與之對應,alloca 函數除外。
  • 每次分配內存之後都應該及時進行初始化,可以結合 memset 函數進行初始化,calloc 函數除外。
  • 每當向指針寫入值時,都要確保對可用字節數和所寫入的字節數進行交叉覈對。
  • 在對指針賦值前,一定要確保沒有內存位置會變爲孤立的。
  • 每當釋放結構化的元素(而該元素又包含指向動態分配的內存位置的指針)時,都應先遍歷子內存位置並從那裏開始釋放,然後再遍歷回父節點。
  • 始終正確處理返回動態分配的內存引用的函數返回值。

轉自c語言中文網 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章