C語言深度剖析---內存泄露

內存泄露幾乎是很難避免的,不管是老手還是新手,都存在這個問題。
     會產生泄露的內存就是堆上的內存(這裏不討論資源,句柄等泄露的情況),也就是說由malloc系列函數或者new操作符分配的內存。如果用完之後沒有及時free或者delete,這塊內存就無法釋放,直到整個程序終止。

1.告老還鄉求良田
怎麼去理解這個內存分配和釋放的過程呢?請看下面這段對話
萬歲爺:愛卿,你爲朕立下了汗馬功勞,想要何賞賜?
某功臣:萬歲,黃金白銀,臣視之如糞土。臣年歲已老,欲告老還鄉。臣乞良田四千畝以萌後世,別無他求
萬歲爺:愛卿,你勞苦功高,卻僅要如此小賞,朕今天就如你所願。戶部侍郎,查看一下湖廣一帶是否還有千畝上等良田未曾封賞。
戶部侍郎:長沙尚有五萬畝上等良田未曾封賞
萬歲爺:在長沙撥四千畝良田封賞愛卿。愛卿,良田四千畝,你欲何利用?
某功臣:謝萬歲,長沙一帶,適合種水稻,臣想用來中水稻。種水稻需要把田分爲一畝一塊,方便耕種。
.........

2如何利用malloc函數
不要莫名其妙,其實上面的小段對話就是malloc的使用過程。malloc是一個函數,專門用來從堆上分配內存。使用malloc函數需要幾個要求:
內存分配給誰?這裏把良田分配給某功臣
分配多大內存?這裏分配4千畝
使用還有足夠內存分配?  這裏還有足夠良田分配。
內存將用來存儲什麼格式的數據,即內存用來做什麼?這裏用來種水稻,還要分爲一塊一塊
分配好的內存在哪裏?這裏在長沙

如果這5點都確定,那內存就能分配。下面看看malloc的原型:
(void *)malloc(int size)
    malloc函數的返回值是一個void類型的指針,參數爲int類型的數據,即申請分配的內存大小,單位是字節。內存分配成功之後,malloc函數返回這塊內存的首地址,你需要一個指針來接受這個地址。但是由於函數的返回值是void *類型,所以必須強制轉換成你所接收的類型。也就是說這塊內存將來要用來存儲什麼類型的數據,比如
char *p = (char *)malloc(100);
在堆上分配了100個字節的內存,返回這塊內存的首地址,把地址強制轉換成char *類型後賦給char *類型的指針變量p;同時告訴我們這塊內存將用來存儲char類型的數據。也就是說你只能通過指針變量p來操作這塊內存。這塊內存本身沒有名字,對它的訪問是匿名訪問
       但是,每次你都能分配成功嗎?不一定。上面的對話,皇帝讓戶部侍郎查詢是否還有足夠的良田未分配出去。使用malloc函數同樣要注意這點:如果所申請的內存塊大於目前堆上剩餘的內存塊(整塊),則內存分配就會失敗,函數函數NULL。注意這裏說的是“堆上剩餘內存塊”不是所有剩餘內存塊之和,因爲malloc函數申請的是連續的一塊內存。
    既然malloc函數申請內存又不成功的可能,那我們在使用指向這塊內存的指針時,必須用if( NULL != p)語句上來驗證內存分配確實成功了。

3.用malloc函數申請0字節的內存
  另外還有一個問題,用malloc函數申請0字節內存會返回NULL指針嗎?
     可以測試一下,也可以與去查找關於malloc函數的說明文檔。申請0字節內存,函數並不返回NULL,而是返回一個正常的內存地址,但是你卻無法使用這塊大小爲0的內存。這好比尺子上的某個刻度,刻度本身並沒有長度,只有某兩個刻度一起才能量出長度。對於這點一定要小心,因爲這時候if(NULL != NULL)將不起作用。

4內存釋放
     既然有分配,那就必須有釋放。不然的話,有限的內存就會用光,而沒有釋放的內存卻在空閒。與malloc對應的就是free函數了。free函數只有一個參數,就是所要釋放的內存塊的首地址。按上例,則爲:
    free(p);
  free函數看上去挺狠的,但他到底做了什麼呢?其實他就做了一件事:斬斷指針變量和這塊內存的關係。比如上面的例子,我們可以說malloc函數分配的內存塊是屬於p的,因爲我們對這塊內存的訪問都需要通過p來進行。free函數就是把這塊內存和p之間的關係斬斷,從此p和那塊內存之間再無瓜葛。(p本身的值並沒有改變!!!相當於野指針)即指針變量p本身保存的地址並沒有改變,但是他對這個地址的那塊內存已經沒有所有權了。那塊被釋放的內存裏面保存的值也沒有改變,只是再也沒有辦法使用了。
   這就是free函數的功能,如果對p連續2次使用free函數,肯定會發生錯誤。因爲第一次使用free函數時,p所屬的內存已經被釋放,第2次使用時,已經沒有內存可以釋放了。所以一夫一妻制。一個malloc,一個free。

5內存釋放後
    既然使用free函數之後,指針變量p本身保存的地址並沒有改變,那我們就需要重新把p的值變爲NULL。
    p = NULL
這個NULL就是我們前面說的“栓野狗的鏈子”,如果你不栓起來遲早會出問題的。比如:在free(p)之後,你用if(NULL != p)這樣的校驗語句還能起作用嗎?
#include <stdio.h>
#include <string.h>
int main(void)
{
char *p = (char *)malloc(100);
strcpy(p,"hello");
free(p);

if(NULL != p)
{
strcpy(p,"world");
}

return 0;

}
釋放完塊內存之後,沒有把指針置NULL,這個指針就成爲了“野指針”,也有書叫“懸垂指針”,這是很危險的,而且也是經常出錯的地方。記住一條,free之後,一定要給指針置NULL
6,內存釋放後,但是繼續通過指針來使用
     a.就是上面所說的,free(p)之後,繼續通過p指針來訪問內存。解決辦法就是給p置NULL
    b.函數返回棧內存,這是初學者最容易犯的錯誤。比如在函數內部定義了一個數組,卻用return語句返回指向該數組的指針。解決的辦法就是弄明白棧上變量的生命週期。
   c.內存使用太複雜,弄不清到底哪塊內存被釋放,哪塊沒有被釋放。解決的辦法是重新設計程序,改善對象之間的調用關係。
   上面詳細討論了常見的6中錯誤及解決對策,希望讀者認真研讀,儘量自己對每種錯誤發生的原因及預防手段都爛熟於心。多調試代碼,多總結經驗
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章