如何使用valgrind查找程序中的内存泄漏?
我正在使用Ubuntu 10.04,并且有一个名为a.c
的程序。
如何使用valgrind查找程序中的内存泄漏?
我正在使用Ubuntu 10.04,并且有一个名为a.c
的程序。
首先检查您是否已安装Valgrind,如果没有:
sudo apt install valgrind # Ubuntu, Debian, etc.
sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
sudo pacman -Syu valgrind # Arch, Manjaro, Garuda, etc.
sudo pkg ins valgrind # FreeBSD
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
--leak-check=full
:“详细显示每个单独的内存泄漏”--show-leak-kinds=all
:在“full”报告中显示所有“definite, indirect, possible, reachable”类型的内存泄漏。--track-origins=yes
:优先考虑有用的输出而非速度。此选项跟踪未初始化值的来源,对于内存错误非常有用。如果Valgrind运行过慢,可以考虑关闭此选项。--verbose
:可以告诉您程序的异常行为。重复使用以获得更多详细信息。--log-file
:将输出写入文件。当输出超出终端空间时非常有用。最后,您希望看到一个类似以下样式的Valgrind报告:
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
所以,你有一个内存泄漏,而Valgrind没有提供任何有意义的信息。 也许,类似这样的情况:
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (in /home/Peri461/Documents/executable)
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
main
和malloc
。在一个更大的程序中,这将会非常麻烦去追踪。这是因为可执行文件是如何编译的。我们实际上可以逐行查看出错的详细信息。重新用调试标志重新编译你的程序(我这里使用的是gcc
)。gcc -o executable -std=c11 -Wall main.c # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c # add -ggdb3 to it
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (main.c:4)
充分利用cppreference!它对C和C++函数有很好的文档。还可以考虑www.cplusplus.com。
关于内存泄漏的一般建议:
如果可以的话,使用RAII,大部分问题都会迎刃而解。
确保你动态分配的内存确实被释放。
不要分配内存然后忘记给指针赋值。
除非旧的内存已经被释放,否则不要用新的指针覆盖旧的指针。
关于内存错误的一般建议:
访问和写入你确定属于自己的地址和索引。内存错误与泄漏不同,它们通常只是IndexOutOfBoundsException
类型的问题。
在释放内存后不要再访问或写入该内存。
有时候你的泄漏/错误可能相互关联,就像一个IDE发现你还没有输入闭合括号一样。解决一个问题可能会解决其他问题,所以寻找一个看起来像罪魁祸首的问题,并应用一些这些想法:
列出你的代码中依赖于/被依赖于“有问题”的代码的函数。跟踪程序的执行(也许甚至在gdb
中),寻找前置条件/后置条件错误。重点是追踪程序的执行,同时关注分配内存的生命周期。
尝试注释掉“有问题”的代码块(合理地注释,以便你的代码仍然可以编译)。如果Valgrind错误消失了,那么你就找到了它的位置。
如果所有其他方法都失败了,尝试查阅资料。Valgrind也有文档!
60 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
by 0x4005E4: resizeArray (main.c:12)
by 0x40062E: main (main.c:19)
而且代码:
#include <stdlib.h>
#include <stdint.h>
struct _List {
int32_t* data;
int32_t length;
};
typedef struct _List List;
List* resizeArray(List* array) {
int32_t* dPtr = array->data;
dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
return array;
}
int main() {
List* array = calloc(1, sizeof(List));
array->data = calloc(10, sizeof(int32_t));
array = resizeArray(array);
free(array->data);
free(array);
return 0;
}
realloc
可以将分配的内存移动到其他位置并更改指针的位置。然后,我们离开了 resizeArray
而没有告诉 array->data
数组被移动到了哪里。
1 errors in context 1 of 1:
Invalid write of size 1
at 0x4005CA: main (main.c:10)
Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
at 0x4C2B975: calloc (vg_replace_malloc.c:711)
by 0x400593: main (main.c:5)
而且代码:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* alphabet = calloc(26, sizeof(char));
for(uint8_t i = 0; i < 26; i++) {
*(alphabet + i) = 'A' + i;
}
*(alphabet + 26) = '\0'; //null-terminate the string?
free(alphabet);
return 0;
}
请注意,Valgrind指向了上面的代码行。大小为26的数组的索引范围是[0,25],这就是为什么*(alphabet + 26)
是一个无效的写入操作——超出了边界。无效的写入是常见的一种因为差一错误而产生的结果。请查看您赋值操作的左侧。
1 errors in context 1 of 1:
Invalid read of size 1
at 0x400602: main (main.c:9)
Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x4005E1: main (main.c:6)
而且代码:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* destination = calloc(27, sizeof(char));
char* source = malloc(26 * sizeof(char));
for(uint8_t i = 0; i < 27; i++) {
*(destination + i) = *(source + i); //Look at the last iteration.
}
free(destination);
free(source);
return 0;
}
*(destination + 26) = *(source + 26);
。然而,*(source + 26)
再次越界,类似于无效写入。无效读取也是因为差一错误的常见结果。看一下你的赋值操作的右侧。
当泄漏是我的时候,我如何知道?当我使用别人的代码时,我如何找到我的泄漏?我发现了一个不属于我的泄漏,我应该做些什么?这些都是合理的问题。首先,我们来看两个真实世界的例子,展示了两类常见的遭遇。
#include <jansson.h>
#include <stdio.h>
int main() {
char* string = "{ \"key\": \"value\" }";
json_error_t error;
json_t* root = json_loads(string, 0, &error); //obtaining a pointer
json_t* value = json_object_get(root, "key"); //obtaining a pointer
printf("\"%s\" is the value field.\n", json_string_value(value)); //use value
json_decref(value); //Do I free this pointer?
json_decref(root); //What about this one? Does the order matter?
return 0;
}
decref
操作。实际上,我上面写的这段代码会引发“无效读取”和“无效写入”两个错误。当你删除value
的decref
行时,这些错误就会消失。value
在Jansson API中被认为是“借用引用”。Jansson会为您跟踪其内存,并且您只需独立地decref
每个JSON结构即可。这里的教训是:阅读文档。真的。有时很难理解,但它们告诉您为什么会发生这些事情。相反,我们对这个内存错误有一些现有问题。
#include "SDL2/SDL.h"
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Quit();
return 0;
}
我如何知道泄漏是我的问题?
就是你的问题。(99%确定)
感谢您一直陪伴我到现在。希望您已经学到了一些东西,因为我试图照顾到各种不同背景的人们。希望您在这个过程中有以下一些问题:C语言的内存分配器是如何工作的?什么是内存泄漏和内存错误?它们与段错误有什么区别?Valgrind是如何工作的?如果您对其中任何一个问题感兴趣,请点击以下链接满足您的好奇心:
要使用此工具,您可以在Valgrind命令行上指定--tool=memcheck。不过,您并不需要这样做,因为Memcheck是默认工具。
- Steven Leegdb
中运行该程序。你可以运行:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
所以,每当您想检查内存泄漏时,请简单执行以下操作
vg ./<name of your executable> <command line parameters to your executable>
这将在当前目录生成一个Valgrind日志文件。