1. 程式人生 > >使用valgrind來發現記憶體洩漏和非法記憶體操作

使用valgrind來發現記憶體洩漏和非法記憶體操作

原文地址:http://www.cprogramming.com/debugging/valgrind.html

翻譯難免會因個人水平原因而有不準確的地方,請大家多批評指正,上面是原文連結,大家也可以直接去看看。

valgrind是Linux平臺一個多用途的程式碼審查和記憶體除錯工具。它可以在valgrind自己的環境中執行你的程式,監控malloc/free,(new/delete for C++)等記憶體呼叫。如果你用了未初始化的記憶體,陣列越界寫入,或者忘了free一個指標,valgrind會檢測到它們。由於這些都是一些日常最普通的問題,這篇文章就主要介紹如何用valgrind來發現這類簡單的記憶體問題,雖然valgrind可以做的更多。

對windows使用者來說,如果你沒有對linux機器的訪問許可權,或者你想開發windows程式,那麼你可能對IBM的Purity軟體更有興趣,Purity在檢測記憶體問題方面與valgrind功能類似,你可以自己去下載它。

獲取Valgrind

如果你正在執行linux,而沒有安裝valgrind,那麼你可以從Valgrind download page下載。

安裝很簡單,只需要解壓就可以了。(XYZ在下面的例子中是版本號的意思)

bzip2 -d valgrind-XYZ.tar.bz2
tar -xf valgrind-XYZ.tar
上面的操作會建立一個目錄,名字為valgrind-XYZ,切換到這個目錄,執行下面的命令
./configure
make
make install
現在你已經安裝了valgrind,讓我們看下怎麼使用它

使用Valgrind查詢記憶體洩漏

記憶體洩漏是最難檢測的bug之一,因為直到你用完了記憶體而有一個malloc失敗,它不會表現出任何外在的問題。實際上,當我們使用C/C++這類沒有垃圾回收機制的語言來工作的時候,幾乎一半的時間會花費在正確處理free記憶體的問題上。如果你的程式執行足夠長的時間並且執行到了那個程式碼分支,即使一個錯誤也是代價巨大的。

當你用valgrind執行你的程式碼的時候,你需要指定使用valgrind的什麼工具;簡單地執行一下valgrind你就會得到當前的列表。在這篇文章中,我們主要使用memcheck工具,memcheck工具可以保證我們正確的記憶體使用。不加其他引數,valgrind會打印出呼叫call和malloc的一個概括資訊(注意18490是我係統上的process id;在不同的執行時,它是不同的)

% valgrind --tool=memcheck program_name
...
=18515== malloc/free: in use at exit: 0 bytes in 0 blocks.
==18515== malloc/free: 1 allocs, 1 frees, 10 bytes allocated.
==18515== For a detailed leak analysis,  rerun with: --leak-check=yes
如果你有一處記憶體洩漏,那麼alloc和free的數目就會不同(你不能用free去釋放一塊屬於多個alloc的記憶體)。我們稍後會回來看這個概況,但是現在,注意一些errors被制止了,因為它們是來自標準庫的,而不是來自你的程式碼。

如果alloc和free的數目不同,你需要用選項--leak-check來重新執行程式。這會向你展示所有的沒有相匹配的free的malloc/new等呼叫。

為了說明,我用一個簡單的程式,我編譯成可執行檔案"example1"

#include <stdlib.h>
int main()
{
    char *x = malloc(100); /* or, in C++, "char *x = new char[100] */
    return 0;
}
% valgrind --tool=memcheck --leak-check=yes example1

這會產生關於上面展示的程式的一些資訊,生成一份列表,呼叫malloc但是沒有相應的free。

==2116== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2116==    at 0x1B900DD0: malloc (vg_replace_malloc.c:131)
==2116==    by 0x804840F: main (in /home/cprogram/example1)
這並沒有按照我們希望的那樣告訴我們太多,儘管我們知道這個記憶體洩漏是發現在main中對malloc的呼叫,但是我們不知道行號。這個問題是因為我們編譯的時候沒有用gcc的-g選項,這個選項會新增除錯符號。因此我們重新編譯,得到下面更有用的資訊。
==2330== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2330==    at 0x1B900DD0: malloc (vg_replace_malloc.c:131)
==2330==    by 0x804840F: main (example1.c:5)
現在我們知道確切的行號,在哪裡這塊洩漏的記憶體被分配。儘管想要跟蹤下去到free的時候,還是個問題,但是至少我們知道從哪開始查起。並且,既然對每一處malloc或new的呼叫,你都有計劃如何處理這塊記憶體,知道記憶體在哪裡洩漏會讓你知道從哪裡開始查詢問題。

有時,選項--leak-check=yes不會向你展示所有的記憶體洩漏。要找到所有的不成對的對free和new的呼叫,你需要使用選項--show-reachable=yes。它的輸出基本是相同的,但是它會向你展示更多unfreed的記憶體。

使用Valgrind發現非法指標使用

Valgrind通過memcheck工具發現非法堆記憶體的使用。例如,如果你用malloc或new分配一個數組,然後嘗試訪問陣列的邊界外位置:

char *x = malloc(10);
x[10] = 'a';
Valgrind會檢測到。例如,用valgrind執行下面的程式example2
#include <stdlib.h>

int main()
{
    char *x = malloc(10);
    x[10] = 'a';
    return 0;
}
執行方法:
valgrind --tool=memcheck --leak-check=yes example2
結果為:
==9814==  Invalid write of size 1
==9814==    at 0x804841E: main (example2.c:6)
==9814==  Address 0x1BA3607A is 0 bytes after a block of size 10 alloc'd
==9814==    at 0x1B900DD0: malloc (vg_replace_malloc.c:131)
==9814==    by 0x804840F: main (example2.c:5)
上面的結果告訴我們:我們正在越界使用一個被分配了10bytes空間的指標,結果有一個‘Invalid write’。如果我們嘗試從那塊記憶體讀資料,我們會被將被警告'Invalid read of size X',此處的X是我們嘗試讀取的記憶體大小(對char型別來說,它是1,對int就是2或者4).通常,Valgrind會打印出函式呼叫的堆疊資訊,我們就會確切地知道錯誤在哪發生。

使用Valgrind檢測未初始化的變數使用

Valgrind還有一個用途,它可以檢測到在條件語句中使用未初始化的值。儘管你應該習慣在建立一個變數的時候進行初始化,但是Valgrind會幫助你發現那些你忘記的地方。例如,執行下面example3的程式碼:

#include <stdio.h>

int main()
{
    int x;
    if(x == 0)
    {
        printf("X is zero"); /* replace with cout and include 
                                iostream for C++ */
    }
    return 0;
}
結果為:
==17943== Conditional jump or move depends on uninitialised value(s)
==17943==    at 0x804840A: main (example3.c:6)
Valgrind會足夠聰明,知道一個變數是否被用一個未初始化的變數賦值,例如,下面的程式碼:
#include <stdio.h>

int foo(int x)
{
    if(x < 10)
    {
        printf("x is less than 10\n");
    }
}

int main()
{
    int y;
    foo(y);
}
結果如下:
==4827== Conditional jump or move depends on uninitialised value(s)
==4827==    at 0x8048366: foo (example4.c:5)
==4827==    by 0x8048394: main (example4.c:14)
你可能會認為問題出在foo函式中,堆疊資訊也不重要。但是,由於main傳了一個未初始化的值給foo函式(你從來沒有給y賦值),那麼我們查詢問題並跟蹤變數的賦值堆疊,直到我們發現了一個未初始化的變數。

這隻會在你的程式走到那個分支的時候幫助你,尤其是條件語句。所以確保在測試的時候能夠讓程式走過所有的分支。

Valgrind能夠發現的其他問題

Valgrind會檢測到其他的一些不恰當的記憶體使用:如果你free一個指標兩次,Valgrind會為你檢測到;你會得到下面錯誤:

Invalid free()
並跟隨一些相關的堆疊

Valgrind也會檢測到釋放記憶體時選擇了錯誤的方法。例如,在C++中,有3中基本的釋放動態記憶體的方法:free,delete,delete[]。free函式只跟malloc匹配,delete只跟new匹配,delete[]只跟new[]匹配。(儘管有些編譯器會為你處理此類用錯delete的情況,但是不能保證所有的編譯器都能,它不是變準要求的)。

如果你觸發了這類問題,你會得到錯誤:

Mismatched free() / delete / delete []

這是必須要修復的,即使你的程式碰巧可以工作。

Valgrind不能發現的東西

Valgrind不能檢查靜態陣列的邊界(在棧上分配的空間)。因此,如果你在你的函式中宣告一個數組

int main()
{
    char x[10];
    x[11] = 'a';
}
那麼,Valgrind不會警告你的!一個可能用於測試目的的解決方案是在你需要做邊界檢測的地方轉換你的靜態陣列為從堆上動態分配記憶體,但是這會產生很多unfreed的記憶體。

其他更過的警告資訊

Valgrind的缺點是什麼呢?它會消耗更多的記憶體--最大兩倍於你源程式需要的記憶體。如果你在檢測一個很大的記憶體問題,那這可能會導致一些問題。它會需要更長的時間去執行你的程式。這通常不應該有什麼問題,並且也只是在你測試的時候有影響而已。但是如果你正在執行一個本來已經很慢的程式,那麼這也可能會是個問題。

總結

Valgrind是一個對x86和AMD64結構的一個工具,執行在Linux環境下。它允許程式設計師在它的環境下執行程式,因此可以檢測不成對的malloc和其他使用非法記憶體(例如未初始化的記憶體)的問題或者非法記憶體操作(例如重複free同一塊記憶體,呼叫錯誤的解構函式)。Valgrind不能檢測靜態記憶體問題。

水平有限,如果有朋友發現錯誤,歡迎留言交流。
轉載請保留本文連結,如果覺得我的文章能幫到您,請頂一下。,謝謝。