C程序内存泄露检测工具——Valgrind

缘起

C/C++程序员需要亲力亲为地管理内存,一不小心就会造成“内存泄露”。说到这儿,有的同学会问:内存泄露是什么意思?

以下摘自 维基百科

在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

除了在编码时小心翼翼,还有什么办法能避免内存泄露呢?这里为大家介绍一个工具——Valgrind. 它的官网是 http://valgrind.org/

正如官网所说:

Valgrind is an instrumentation framework for building dynamic analysis tools. There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. You can also use Valgrind to build new tools.

Valgrind 提供了许多调试和分析工具,这些工具中最流行的称为 Memcheck. 它可以检测出许多在C/C ++程序中常见的与内存有关的错误。

Valgrind 的安装

图个省事,咱们就不从源码安装了。

sudo apt install valgrind

这是一个没有界面的内存检测工具。安装后,输入valgrind ls -l验证一下该工具是否能用。(这是README里面的方法,实际上是执行对ls -l命令的内存检测。)如果你看到类似下图的信息,说明安装成功。
这里写图片描述

Valgrind 的使用

首先准备好源码,然后进行编译。官网是这样说的:

Compile your program with -g to include debugging information so that Memcheck’s error messages include exact line numbers. Using -O0 is also a good idea, if you can tolerate the slowdown. With -O1 line numbers in error messages can be inaccurate, although generally speaking running Memcheck on code compiled at -O1 works fairly well, and the speed improvement compared to running -O0 is quite significant. Use of -O2 and above is not recommended as Memcheck occasionally reports uninitialised-value errors which don’t really exist.

总之要加-g选项,另外优化级别建议0或1级别,即加-O0或者-O1.

编译后,假设生成的可执行文件是a.out,那么请输入

    valgrind  ./a.out

如果你的程序有命令行参数,那么跟在后面就行。比如

    valgrind  ./a.out arg1 arg2

举例

我的上一篇博文——堆的C语言实现——堆与堆排序(二)讲了堆的初始化、构造、插入、删除等,刚好用到了mallocfree函数,咱们就拿这个举例。

只分配,不释放

//测试函数
int main(void)
{
    int a[]={4,1,3,2,16,9,10,14,8,7}; //10个
    int elmt_cnt = sizeof(a) / sizeof(a[0]);

    priority_queue pq1,pq2,pq3,pq4;

    // 测试最大堆构造函数
    pq1 = build_max_heap_bottom_up_recursive(a,elmt_cnt);
    pq2 = build_max_heap_bottom_up(a,elmt_cnt); 
    pq3 = build_max_heap_up_bottom(a,elmt_cnt);
    pq4 = build_max_heap_up_bottom2(a,elmt_cnt);

    // 打印构造结果
    print_heap(pq1);
    print_heap(pq2);
    print_heap(pq3);
    print_heap(pq4);

    // 测试删除最大键的函数
    int del_key = 0;

    printf("delete the max key: ");
    while(is_empty(pq3)==0) // 用pq3测试
    {
        del_key = delete_max(pq3);
        printf("%d ", del_key);
    }
    printf("\n");

   // destroy(pq1); //别忘了释放内存!
   // destroy(pq2);
   // destroy(pq3);
   // destroy(pq4);   

    return 0;   
}

把后面的destroy函数都注释掉,编译后用 Valgrind 工具检测,结果如下图。
这里写图片描述

Valgrind 报告显示,在程序退出的时候,还有 240 字节的内存在使用,也就是没有释放。
我们看看源码,这240字节是怎么来的?
不管用什么方式构造堆,都会调用初始化函数:


struct heap_struct
{
    int capacity;
    int size;
    element_t *elements;
};
...
priority_queue initialize(int capacity)
{
    priority_queue heap;

    ...
    heap = malloc( sizeof(struct heap_struct));
    ...

    // +1 是因为要包含[0]
    heap->elements = malloc( (capacity + 1) * sizeof(element_t) );
    ...

    ...
    return heap;
}

我们计算一下这个初始化函数需要申请多少内存。结构体struct heap_struct占用的内存是 16(=4+4+8,我的系统是 x64 的 Ubuntu,指针占8字节)。heap->elements指向的空间大小是44(=4*11)。所以共申请内存60(= 16+44)字节。

main函数中构造了4个堆,所以共申请内存240(=60*4)字节,这和检测结果相符。

分配后正确释放

加上main函数最后的destroy(pq1)等4条语句,编译后再用 Valgrind 检测,结果如下图。
这里写图片描述
可以看出,程序退出时,还在使用的内存大小是0;所有的堆空间都被释放,没有内存泄露。

【完】

参考资料
https://blog.csdn.net/gatieme/article/details/51959654

猜你喜欢

转载自blog.csdn.net/u013490896/article/details/80247806