大家都知道,在堆上分配的內存,如果不再使用了,就應該及時釋放,以便後面其他地方可以重用。而在 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語言中文網