如上所述,内存管理容易出现难以检测的错误。常见的错误可以列举如下:
这些错误通常会导致崩溃。
这就是我们需要 Valgrind 的情况。 Valgrind 直接与可执行文件一起工作,无需重新编译、重新链接或修改要检查的程序。 Valgrind 决定是否应修改程序以避免内存泄漏,并指出"泄漏"的位置。
Valgrind 模拟程序执行的每条指令。因此,Valgrind 不仅会在您的应用程序中查找错误,还会在所有支持的动态链接(.so 格式)库中查找错误,包括 GNU C 库、X 客户端库、Qt(如果您使用 KDE)等等。这通常包括库,例如 GNU C 库,其中可能包含内存访问违规。
只需在用于调用程序的常规命令之前加上单词 valgrind 即可执行检查。 例如
#valgrind ps -ax |
==1353== Invalid read of size 4 ==1353== at 0x80484F6: print (valg_eg.c:7) ==1353== by 0x8048561: main (valg_eg.c:16) ==1353== by 0x4026D177: __libc_start_main (../sysdeps/generic/libc-start.c :129) ==1353== by 0x80483F1: free@@GLIBC_2.0 (in /home/deepu/valg/a.out) ==1353== Address 0x40C9104C is 0 bytes after a block of size 40 alloc'd ==1353== at 0x40046824: malloc (vg_clientfuncs.c:100) ==1353== by 0x8048524: main (valg_eg.c:12) ==1353== by 0x4026D177: __libc_start_main (../sysdeps/generic/libc-start.c :129) ==1353== by 0x80483F1: free@@GLIBC_2.0 (in /home/deepu/valg/a.out) |
Valgrind 实际上只能检测到两种类型的错误:使用非法地址和使用未定义的值。 然而,这足以发现程序中各种内存管理问题。 下面给出一些常见的错误。
这对于calloc不是问题,因为它使用 0 初始化每个分配的字节。newC++ 中的运算符类似于malloc. 创建的对象的字段将未初始化。
#include <stdlib.h> int main() { int p, t; if (p == 5) /*Error occurs here*/ t = p+1; return 0; } |
这里的值p未初始化,因此p可能包含一些随机值(垃圾),因此在条件检查时可能会发生错误。 未初始化的变量会在两种情况下导致错误
当您尝试从/向不在程序地址范围内的地址读取/写入时,会发生非法读取/写入错误。
#include <stdlib.h> int main() { int *p, i, a; p = malloc(10*sizeof(int)); p[11] = 1; /* invalid write error */ a = p[11]; /* invalid read error */ free(p); return 0; } |
Valgrind 跟踪使用以下命令分配给程序的块malloc/new. 因此,它可以轻松检查 free/delete 的参数是否有效。
#include <stdlib.h> int main() { int *p, i; p = malloc(10*sizeof(int)); for(i = 0;i < 10;i++) p[i] = i; free(p); free(p); /* Error: p has already been freed */ return 0; } |
在 C++ 中,您可以使用多个函数分配和释放内存,但必须遵守以下规则
#include <stdlib.h> int main() { int *p, i; p = ( int* ) malloc(10*sizeof(int)); for(i = 0;i < 10;i++) p[i] = i; delete(p); /* Error: function mismatch */ return 0; } |
==1066== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ==1066== malloc/free: in use at exit: 0 bytes in 0 blocks. ==1066== malloc/free: 1 allocs, 1 frees, 40 bytes allocated. ==1066== For a detailed leak analysis, rerun with: --leak-check=yes ==1066== For counts of detected errors, rerun with: -v |
#include <stdlib.h> #include <unistd.h> int main() { int *p; p = malloc(10); read(0, p, 100); /* Error: unaddressable bytes */ free(p); return 0; } |
==1045== Syscall param read(buf) contains unaddressable byte(s) ==1045== at 0x4032AF44: __libc_read (in /lib/i686/libc-2.2.2.so) ==1045== by 0x4026D177: __libc_start_main (../sysdeps/generic/libc-start.c:129) ==1045== by 0x80483E1: read@@GLIBC_2.0 (in /home/deepu/valg/a.out) |
这里,buf = p包含一个 10 字节块的地址。read系统调用尝试从标准输入读取 100 个字节,并将其放置在p. 但是前 10 个字节之后的字节是不可寻址的。
#include <stdlib.h> int main() { int *p, i; p = malloc(5*sizeof(int)); for(i = 0;i < 5;i++) p[i] = i; return 0; } |
==1048== LEAK SUMMARY: ==1048== definitely lost: 20 bytes in 1 blocks. ==1048== possibly lost: 0 bytes in 0 blocks. ==1048== still reachable: 0 bytes in 0 blocks. |
可以修改抑制文件。 如果您的项目的一部分包含您无法或不想修复的错误,但您不想不断地被提醒它们,这将非常有用。 文件的格式如下。
{ Error name Type fun:function name, which contains the error to suppress fun:function name, which calls the function specified above } |
Error name can be any name. type=ValueN, if the error is an uninitialized value error. =AddrN, if it is an address error.(N=sizeof(data type)) =Free, if it is a free error (eg:mismatched free) =Cond, if error is due to uninitialized CPU condition code. =Param, if it is an invalid system call parameter error. |
valgrind --suppressions=path/to/the/supp_file.supp testprog |