1. 程式人生 > >C++常見記憶體錯誤及解決方案

C++常見記憶體錯誤及解決方案

轉自 本文作者:Tocy e-mail: [email protected]

C++中記憶體錯誤通常屬於執行時錯誤,只有在程式執行時才能發現,編譯器無法自動檢測到記憶體錯誤。多數情況下是程式邏輯或者引數存在某些錯誤。下面總結一下C++常見的記憶體錯誤:

1. 記憶體洩露

記憶體洩露是指應用程式未釋放動態申請的且不再使用的記憶體,原因可能是程式設計師疏忽或者錯誤造成程式異常。

在C/C++中,動態申請的記憶體是在堆上的。記憶體管理器也不會自動回收不再使用的記憶體,也就是說如果忘記釋放動態申請的記憶體,該記憶體區域是不允許重用的。如果傳送此類的記憶體洩露,函式每執行一次就丟失一塊記憶體。長時間執行改程式可能引起系統"記憶體耗盡"。

這個問題本身沒有很好的解決思路。只能從程式設計習慣上入手,也就是說動態申請記憶體與釋放記憶體必須匹配,亦即new和delete的呼叫次數必須相同。

2. 野指標

未初始化的指標稱為野指標(另一種說法是指向不可用記憶體區域的指標,不過筆者認為前者更合適)。通常對野指標進行寫操作會發生不可預知的錯誤。

通常的避免方法就是在指標定義的時候就初始化,初始為NULL或者一個有意義的記憶體地址。對於動態申請的記憶體地址,在該記憶體釋放之後,對應指標最好立即賦值為NULL。並在具體使用指標的時候判斷指標的值是否有效(通常檢測是否為NULL)。

3. 記憶體越界訪問

記憶體越界訪問通常發生在陣列、字串或者連續記憶體的訪問。有兩種情況:

讀越界,即讀了非有效的資料。如果所讀的記憶體地址是無效的,程式會立即崩潰。如果所讀記憶體地址是有效的,讀入的時候不會有錯誤,但是讀入的資料是隨機的,可能會產生不可控制的後果。舉個簡單的例子,字串的輸出,如果沒有結束符,會輸出一堆亂碼也可能輸出正常,也就是說結果是不可控的。

寫越界,亦稱為緩衝區溢位,通常寫越界會發生錯誤。記憶體寫越界造成的後果是非常嚴重的。例如訪問陣列越界可能會修改訪問陣列的迴圈變數,造成死迴圈。另一個比較經典的例子就是緩衝區溢位攻擊,試試上就是利用越界修改程式的棧空間,達到控制作業系統或者執行某些特定任務的目的。

這類問題幾乎沒有很有效的解決思路,只能由程式設計師控制好記憶體的訪問,小心處理各種記憶體有關的操作。

4. 返回指向臨時變數的指標

最經典的例子是一道面試題中關於字串指標的返回函式,程式碼如下:

char * getString(){char b[] = "Hello, Tocy!"; return b;}

棧裡面的變數都是臨時的,函式執行完成之後,相關的臨時變數和引數都會被清除。這也是程式不允許返回指向這些臨時變數的指標的原因,因為函式執行結束後這些指標指向的資料是隨機的,給程式造成不可預知的後果。

通常此類錯誤編譯器會給出警告。解決思路很簡單,在任何情況下不要返回函式的區域性變數的任何指標,如果需要傳遞引數可以考慮使用返回值、引數或者全域性變數(不推薦)。

5. 試圖修改常量

在普通變數前面加上const修飾符,只是給編譯器做型別檢查用的。編譯器禁止修改這樣的變數,但這並不是強制的,完全可以用強制型別轉換來處理,一般不會出錯。例如下面程式碼:

int func(void){ const int IMAX = 3; int * pi = (int *)(&IMAX); *pi = 4; cout << IMAX << endl;}

筆者在vc6和vs2008下執行該函式,輸出都是3,編譯執行沒有任何錯誤和警告。至於有沒有修改常量的值,有興趣的讀者可以自己看看反彙編的程式碼,相信你就會明白是否修改了常量的值(最終結果是修改了,只是編譯器做了某些優化所以輸出依然不變。)。

而對於全域性常量和字串使用強制型別轉來處理在執行時仍然會出錯,這是因為它們是存放在只讀記憶體區("rodata"),只讀記憶體區是不允許修改的。試圖對其修改,會引發記憶體錯誤。

所以針對這種型別錯誤,筆者建議最好不要修改常量,除非萬不得已。

6. 記憶體未分配成功,但已經使用

通常是由於程式設計師認為動態記憶體分配不會失敗。解決思路很簡單,在使用動態申請的記憶體時,首先檢測指標是否為NULL(通常動態記憶體分配失敗會返回空指標)。

7. 記憶體分配成功,但沒有初始化

犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值錯誤(例如陣列)。

記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。

當然發生這種情況還有另外一個可能就是在動態建立類物件時類建構函式丟擲異常(即申請動態記憶體成功,但是呼叫建構函式失敗)。

另外如果出現下圖

基本可以斷定是記憶體問題,極有可能是指標異常引起的。當然還有其他原因,需要視具體情況而定。