利用Valgrind進(jìn)行內(nèi)存泄露檢查

Note: Valgrind is Linux only. If you aren't running Linux, or want a tool designed from the start to make debugging segfaults and memory issues easier, check out Cee Studio, a fully online C and C++ development environment from our sponsor. Cee Studio provides instant and informative feedback on memory issues.Valgrind is a multipurpose code profiling and memory debugging tool for Linux when on the x86 and, as of version 3, AMD64, architectures. It allows you to run your program in Valgrind's own environment that monitors memory usage such as calls to malloc and free (or new and delete in C++). If you use uninitialized memory, write off the end of an array, or forget to free a pointer, Valgrind can detect it. Since these are particularly common problems, this tutorial will focus mainly on using Valgrind to find these types of simple memory problems, though Valgrind is a tool that can do a lot more.

Alternatively, for Windows users who want to develop Windows-specific software, you might be interested in IBM's Purify, which has features similar to Valgrind for finding memory leaks and invalid memory accesses. A trial download is available.

Getting Valgrind

If you're running Linux and you don't have a copy already, you can get Valgrind from the Valgrind download page.

Installation should be as simple as decompressing and untarring using bzip2 (XYZ is the version number in the below examples)

bzip2 -d valgrind-XYZ.tar.bz2
tar -xf valgrind-XYZ.tar

which will create a directory called valgrind-XYZ; change into that directory and run

./configure
make
make install

Now that you have Valgrind installed, let's look at how to use it.

Finding Memory Leaks With Valgrind

Memory leaks are among the most difficult bugs to detect because they don't cause any outward problems until you've run out of memory and your call to malloc suddenly fails. In fact, when working with a language like C or C++ that doesn't have garbage collection, almost half your time might be spent handling correctly freeing memory. And even one mistake can be costly if your program runs for long enough and follows that branch of code.

When you run your code, you'll need to specify the tool you want to use; simply running valgrind will give you the current list. We'll focus mainly on the memcheck tool for this tutorial as running valgrind with the memcheck tool will allow us to check correct memory usage. With no other arguments, Valgrind presents a summary of calls to free and malloc: (Note that 18490 is the process id on my system; it will differ between runs.)

# 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

If you have a memory leak, then the number of allocs and the number of frees will differ (you can't use one free to release the memory belonging to more than one alloc). We'll come back to the error summary later, but for now, notice that some errors might be suppressed -- this is because some errors will be from standard library routines rather than your own code.

If the number of allocs differs from the number of frees, you'll want to rerun your program again with the leak-check option. This will show you all of the calls to malloc/new/etc that don't have a matching free.

For demonstration purposes, I'll use a really simple program that I'll compile to the executable called "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

This will result in some information about the program showing up, culminating in a list of calls to malloc that did not have subsequent calls to 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)

This doesn't tell us quite as much as we'd like, though -- we know that the memory leak was caused by a call to malloc in main, but we don't have the line number. The problem is that we didn't compile using the -g option of gcc, which adds debugging symbols. So if we recompile with debugging symbols, we get the following, more useful, output:

==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)

Now we know the exact line where the lost memory was allocated. Although it's still a question of tracking down exactly when you want to free that memory, at least you know where to start looking. And since for every call to malloc or new, you should have a plan for handling the memory, knowing where the memory is lost will help you figure out where to start looking.

There will be times when the --leak-check=yes option will not result in showing you all memory leaks. To find absolutely every unpaired call to free or new, you'll need to use the --show-reachable=yes option. Its output is almost exactly the same, but it will show more unfreed memory.

Finding Invalid Pointer Use With Valgrind

Valgrind can also find the use of invalid heap memory using the memcheck tool. For instance, if you allocate an array with malloc or new and then try to access a location past the end of the array:

char *x = malloc(10);
x[10] = 'a';

Valgrind will detect it. For instance, running the following program, example2, through Valgrind

#include <stdlib.h>

int main()
{
    char *x = malloc(10);
    x[10] = 'a';
    return 0;
}

with

valgrind --tool=memcheck --leak-check=yes example2

results in the following warning

==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)

What this tell us is that we're using a pointer allocated room for 10 bytes, outside that range -- consequently, we have an 'Invalid write'. If we were to try to read from that memory, we'd be alerted to an 'Invalid read of size X', where X is the amount of memory we try to read. (For a char, it'll be one, and for an int, it would be either 2 or 4, depending on your system.) As usual, Valgrind prints the stack trace of function calls so that we know exactly where the error occurs.

Detecting The Use Of Uninitialized Variables

Another type of operation that Valgrind will detect is the use of an uninitialized value in a conditional statement. Although you should be in the habit of initializing all variables that you create, Valgrind will help find those cases where you don't. For instance, running the following code as 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;
}

through Valgrind will result in

==17943== Conditional jump or move depends on uninitialised value(s)
==17943==    at 0x804840A: main (example3.c:6)

Valgrind is even smart enough to know that if a variable is assigned the value of an uninitialized variable, that that variable is still in an "uninitialized" state. For instance, running the following code:

#include <stdio.h>

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

int main()
{
    int y;
    foo(y);
}

in Valgrind as example4 results in the following warning:

==4827== Conditional jump or move depends on uninitialised value(s)
==4827==    at 0x8048366: foo (example4.c:5)
==4827==    by 0x8048394: main (example4.c:14)

You might think that the problem was in foo, and that the rest of the call stack probably isn't that important. But since main passes in an uninitialized value to foo (we never assign a value to y), it turns out that that's where we have to start looking and trace back the path of variable assignments until we find a variable that wasn't initialized.

This will only help you if you actually test that branch of code, and in particular, that conditional statement. Make sure to cover all execution paths during testing!

What else will Valgrind Find

Valgrind will detect a few other improper uses of memory: if you call free twice on the same pointer value, Valgrind will detect this for you; you'll get an error:

Invalid free()

along with the corresponding stack trace.

Valgrind also detects improperly chosen methods of freeing memory. For instance, in C++ there are three basic options for freeing dynamic memory: free, delete, and delete[]. The free function should only be matched with a call to malloc rather than a call to, say, delete -- on some systems, you might be able to get away with not doing this, but it's not very portable. Moreover, the delete keyword should only be paired with the new keyword (for allocation of single objects), and the delete[] keyword should only be paired with the new[] keyword (for allocation of arrays). (Though some compilers will allow you to get away with using the wrong version of delete, there's no guarantee that all of them will. It's just not part of the standard.)

If you do trigger one of these problems, you'll get this error:

 Mismatched free() / delete / delete []

which really should be fixed even if your code happens to be working.

What Won't Valgrind Find?

Valgrind doesn't perform bounds checking on static arrays (allocated on the stack). So if you declare an array inside your function:

int main()
{
    char x[10];
    x[11] = 'a';
}

then Valgrind won't alert you! One possible solution for testing purposes is simply to change your static arrays into dynamically allocated memory taken from the heap, where you will get bounds-checking, though this could be a mess of unfreed memory.

A Few More Caveats

What's the drawback of using Valgrind? It's going to consume more memory -- up to twice as much as your program normally does. If you're testing an absolutely huge memory hog, you might have issues. It's also going to take longer to run your code when you're using Valgrind to test it. This shouldn't be a problem most of the time, and it only affects you during testing. But if you're running an already slow program, this might affect you.

Finally, Valgrind isn't going to detect every error you have -- if you don't test for buffer overflows by using long input strings, Valgrind won't tell you that your code is capable of writing over memory that it shouldn't be touching. Valgrind, like another other tool, needs to be used intelligently as a way of illuminating problems.

Summary

Valgrind is a tool for the x86 and AMD64 architectures and currently runs under Linux. Valgrind allows the programmer to run the executable inside its own environment in which it checks for unpaired calls to malloc and other uses of invalid memory (such as ininitialized memory) or invalid memory operations (such as freeing a block of memory twice or calling the wrong deallocator function). Valgrind does not check use of statically allocated arrays.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末代承,一起剝皮案震驚了整個濱河市摄狱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灵迫,死亡現(xiàn)場離奇詭異书幕,居然都是意外死亡象迎,警方通過查閱死者的電腦和手機香伴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門慰枕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人即纲,你說我怎么就攤上這事具帮。” “怎么了低斋?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵蜂厅,是天一觀的道長。 經(jīng)常有香客問我膊畴,道長掘猿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任唇跨,我火速辦了婚禮稠通,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘买猖。我一直安慰自己改橘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布玉控。 她就那樣靜靜地躺著飞主,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奸远。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天讽挟,我揣著相機與錄音懒叛,去河邊找鬼。 笑死耽梅,一個胖子當(dāng)著我的面吹牛薛窥,可吹牛的內(nèi)容都是我干的愕贡。 我是一名探鬼主播舵盈,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掐松!你這毒婦竟也來了众旗?” 一聲冷哼從身側(cè)響起罢杉,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贡歧,沒想到半個月后滩租,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赋秀,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年律想,在試婚紗的時候發(fā)現(xiàn)自己被綠了猎莲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡技即,死狀恐怖著洼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情而叼,我是刑警寧澤身笤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站澈歉,受9級特大地震影響展鸡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜埃难,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一莹弊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涡尘,春花似錦忍弛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至川梅,卻和暖如春疯兼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贫途。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工吧彪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丢早。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓姨裸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親怨酝。 傳聞我的和親對象是個殘疾皇子傀缩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容