1. 程式人生 > >C語言深度剖析---記憶體洩露

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中錯誤及解決對策,希望讀者認真研讀,儘量自己對每種錯誤發生的原因及預防手段都爛熟於心。多除錯程式碼,多總結經驗

轉自:http://zhangzhenyuan163.blog.163.com/blog/static/85819389201262934456815/