(更新中……)

概述

本文通过一些简单案例来介绍如何使用valgrind对C++程序进行内存检测。

valgrind的安装和使用

valgrind只能在Linux系统上安装运行,windows用户可以在wsl系统中安装valgrind。具体方法见另一篇文章:C++在windows下安装wsl和c++编译工具。

使用方法:

1
2
3
4
5
6
7
8
// 编译时带-g参数以包含debug信息:
g++ -g test.cpp -o test.exe

// 使用valgrind启动exe文件:(默认参数 --tool=memcheck 可省略)
valgrind ./test.exe

// 查看所有指令
valgrind --help

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>
using namespace std;

struct Text
{
Text(string in):_data(in){}
void print(){
cout << _data << endl;
}
string _data;
};

int main(){
Text *p = new Text("Hello");
p->print();
return 0;
}

valgrind输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
==67184== Memcheck, a memory error detector
==67184== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==67184== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==67184== Command: ./test.exe
==67184==
Hello
==67184==
==67184== HEAP SUMMARY:
==67184== in use at exit: 32 bytes in 1 blocks
==67184== total heap usage: 3 allocs, 2 frees, 73,760 bytes allocated
==67184==
==67184== LEAK SUMMARY:
==67184== definitely lost: 32 bytes in 1 blocks
==67184== indirectly lost: 0 bytes in 0 blocks
==67184== possibly lost: 0 bytes in 0 blocks
==67184== still reachable: 0 bytes in 0 blocks
==67184== suppressed: 0 bytes in 0 blocks
==67184== Rerun with --leak-check=full to see details of leaked memory
==67184==
==67184== For lists of detected and suppressed errors, rerun with: -s
==67184== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

打印更多信息:(–leak-check=full),可以看到log中增加了报错的行号位置信息:
valgrind --leak-check=full ./test.exe

1
2
3
==67281== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==67281== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==67281== by 0x10A4DA: main (test.cpp:15)

将valgrind的log输出到文件”log.txt”中:(–log-file=log.txt)
valgrind --leak-check=full --log-file=log.txt ./test.exe

valgrind典型案例与log解读

1. 明确泄漏 (definitely lost)

定义了但没有释放的内存块,可能导致内存泄漏。上面的test.cpp就一个明确泄漏的典型例子(比如new之后没有delete)。

2. 间接泄漏 (Indirectly lost)

如果 definitely lost的对象是一个指针,而这个指针指向另外一个对象,这个对象就成为了间接泄漏的情况。

3 可能泄漏 (possibly lost)

4 仍可访达 (still reachable)

5 非法读/写 (Invalid read/write)

1
2
3
4
5
6
7
#include <iostream>
using namespace std;

int main(){
int *p;
cout << *p << endl;
}

valgrind输出:(直接使用未赋值的指针指向的值,会触发invalid read)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
==93102== Use of uninitialised value of size 8
==93102== at 0x1091B9: main (test.cpp:6)
==93102==
==93102== Invalid read of size 4
==93102== at 0x1091B9: main (test.cpp:6)
==93102== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==93102==
==93102==
==93102== Process terminating with default action of signal 11 (SIGSEGV)
==93102== Access not within mapped region at address 0x0
==93102== at 0x1091B9: main (test.cpp:6)
==93102== If you believe this happened as a result of a stack
==93102== overflow in your program's main thread (unlikely but
==93102== possible), you can try to increase the size of the
==93102== main thread stack using the --main-stacksize= flag.
==93102== The main thread stack size used in this run was 8388608.

6 使用未初始化的变量 (uninitialised value)

未初始化的变量如果直接使用其值可能会导致 conditional jump:(未初始化的变量的值是未定义的,不同的编译环境可能有不同的初值,导致程序不可控)

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main(){
int x;
if (x == 0)
cout << "x==0" << endl;
else
cout << "x!=0" << endl;
}

valgrind输出:

1
2
==80600== Conditional jump or move depends on uninitialised value(s)
==80600== at 0x1091B9: main (test.cpp:6)

参考链接