如何使用Valgrind检查内存泄漏

时间:2020-03-05 15:31:18  来源:igfitidea点击:

Valgrind是命令行工具的集合,可用于在Linux中调试和分析可执行文件。
Memcheck是Valgrind工具套件中最受欢迎的工具之一,可用于检测程序可执行文件中与内存相关的错误。

在本教程中,我们将通过一些示例学习如何使用valgrind和memcheck工具来检查内存泄漏。

使用Memcheck检查内存泄漏

Valgrind的Memcheck是功能强大的工具,可以检测与内存相关的各种问题,例如:

  • 没有任何free()的malloc()
  • 一个具有多个free()的malloc()
  • 未初始化的变量
  • 堆指针无效的内存访问

Valgrind与Memcheck工具一起可以按以下方式使用:

valgrind --tool=memcheck --leak-check=yes --track-origins=yes [executable-name]

因此,只需将[executable-name]替换为Valgrind和Memcheck的实际可执行文件名称即可测试并显示错误。
现在,让我们讨论如何使用Memcheck来检测与内存相关的各种问题。

注意使用-g标志编译所有可执行文件(使用gcc时),以便Valgrind工具可以在其输出中产生行号。

1.检测未初始化的变量

假设程序包含一个(或者多个)未初始化的变量。

例如 :

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int a;
if(a)
a = a+1;
return 0;
}

现在,在此代码上运行Valgrind的Memcheck工具时,将产生以下输出:

$valgrind --tool=memcheck --leak-check=yes --track-origins=yes ./executable1
==2897== Memcheck, a memory error detector
==2897== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==2897== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==2897== Command: ./executable1
==2897==
==2897== Conditional jump or move depends on uninitialised value(s)
==2897== at 0x4004F4: main (valgrind_unini_var.c:7)
==2897== Uninitialised value was created by a stack allocation
==2897== at 0x4004EC: main (valgrind_unini_var.c:5)
==2897==
==2897==
==2897== HEAP SUMMARY:
==2897== in use at exit: 0 bytes in 0 blocks
==2897== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==2897==
==2897== All heap blocks were freed -- no leaks are possible
==2897==
==2897== For counts of detected and suppressed errors, rerun with: -v
==2897== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

因此,我们可以看到Valgrind产生了与未初始化变量'a'相关的错误(以粗体突出显示)。

2.检测内存泄漏

假设程序不包含与malloc()相对应的free()。
这将导致内存泄漏。
Memcheck工具可用于轻松找到这些类型的泄漏。

这是代码示例:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
return 0;
}

这是使用Valgrind的Memcheck工具运行从以上代码编译而来的可执行文件时的输出:

$valgrind --tool=memcheck --leak-check=yes --track-origins=yes ./executable2
==2934== Memcheck, a memory error detector
==2934== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==2934== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==2934== Command: ./executable2
==2934==
==2934==
==2934== HEAP SUMMARY:
==2934== in use at exit: 10 bytes in 1 blocks
==2934== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==2934==
==2934== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2934== at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2934== by 0x40053D: main (valgrind_no_free.c:6)
==2934==
==2934== LEAK SUMMARY:
==2934== definitely lost: 10 bytes in 1 blocks
==2934== indirectly lost: 0 bytes in 0 blocks
==2934== possibly lost: 0 bytes in 0 blocks
==2934== still reachable: 0 bytes in 0 blocks
==2934== suppressed: 0 bytes in 0 blocks
==2934==
==2934== For counts of detected and suppressed errors, rerun with: -v
==2934== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

因此,我们可以看到很容易检测到内存泄漏。

3.检测双重释放

有时,程序员犯了一个愚蠢的错误:两次释放相同的内存。
Valgrind的Memcheck工具也可用于检测这些类型的问题。

这是一个包含double free的示例代码:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
free(ptr);
free(ptr);
return 0;
}

这是上述代码的Valgrind Memcheck工具的输出:

$valgrind --tool=memcheck --leak-check=yes --track-origins=yes ./executable3
==2961== Memcheck, a memory error detector
==2961== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==2961== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==2961== Command: ./executable3
==2961==
==2961== Invalid free()/delete/delete[]/realloc()
==2961== at 0x4C2BA6C: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2961== by 0x4005A9: main (valgrind_double_free.c:8)
==2961== Address 0x51fc040 is 0 bytes inside a block of size 10 free'd
==2961== at 0x4C2BA6C: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2961== by 0x40059D: main (valgrind_double_free.c:7)
==2961==
==2961==
==2961== HEAP SUMMARY:
==2961== in use at exit: 0 bytes in 0 blocks
==2961== total heap usage: 1 allocs, 2 frees, 10 bytes allocated
==2961==
==2961== All heap blocks were freed -- no leaks are possible
==2961==
==2961== For counts of detected and suppressed errors, rerun with: -v
==2961== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

因此,我们可以看到第二个free()已成功识别(以粗体突出显示)为无效的函数调用。

4.检测悬空指针上的操作

悬空指针是指向已经释放的内存的指针。
Valgrind的Memcheck可以轻松检测到此类问题。

这是一个示例代码:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
free(ptr);
ptr[3] = 'a';
return 0;
}

因此,我们可以看到上面的代码即使在释放后仍尝试访问“ ptr”指向的内存。
以下是Memcheck检测此错误的方式:

$valgrind --tool=memcheck --leak-check=yes --track-origins=yes ./executable4
==3393== Memcheck, a memory error detector
==3393== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==3393== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==3393== Command: ./executable4
==3393==
==3393== Invalid write of size 1
==3393== at 0x4005A6: main (valgrind_no_null.c:9)
==3393== Address 0x51fc043 is 3 bytes inside a block of size 10 free'd
==3393== at 0x4C2BA6C: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3393== by 0x40059D: main (valgrind_no_null.c:7)
==3393==
==3393==
==3393== HEAP SUMMARY:
==3393== in use at exit: 0 bytes in 0 blocks
==3393== total heap usage: 1 allocs, 1 frees, 10 bytes allocated
==3393==
==3393== All heap blocks were freed -- no leaks are possible
==3393==
==3393== For counts of detected and suppressed errors, rerun with: -v
==3393== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

因此,我们可以看到该工具检测到了悬空指针上的写入操作,并将其标记为无效。

5.检测无效的内存访问

有时,代码中包含一个错误,即一个指针试图访问绑定堆内存位置之外的错误。
Memcheck可以轻松检测与内存相关的此类错误。

这是一个例子:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
ptr[11] = 'z';
return 0;
}

在上面的代码中,我们可以看到指针“ ptr”正在尝试访问超出其范围的内存位置。
这是memcheck检测到此问题的方式:

$valgrind --tool=memcheck --leak-check=yes --track-origins=yes ./executable5
==3538== Memcheck, a memory error detector
==3538== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==3538== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==3538== Command: ./executable5
==3538==
==3538== Invalid write of size 1
==3538== at 0x40059A: main (valgrind_inv_mem.c:7)
==3538== Address 0x51fc04b is 1 bytes after a block of size 10 alloc'd
==3538== at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3538== by 0x40058D: main (valgrind_inv_mem.c:6)
==3538==
==3538==
==3538== HEAP SUMMARY:
==3538== in use at exit: 0 bytes in 0 blocks
==3538== total heap usage: 1 allocs, 1 frees, 10 bytes allocated
==3538==
==3538== All heap blocks were freed -- no leaks are possible
==3538==
==3538== For counts of detected and suppressed errors, rerun with: -v
==3538== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

因此,我们可以看到memcheck很容易检测到(以粗体突出显示)无效的内存访问。

Memcheck工具的局限性

以下是使用Valgrind的一些已知限制:

  • 它减慢了程序的整体处理速度。
  • 如果使用堆栈变量,则无法检测到缓冲区溢出。
  • 它消耗大量内存。