如何使用Valgrind检查内存泄漏
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的一些已知限制:
- 它减慢了程序的整体处理速度。
- 如果使用堆栈变量,则无法检测到缓冲区溢出。
- 它消耗大量内存。