如何使用 valgrind 查找内存泄漏?

Posted

技术标签:

【中文标题】如何使用 valgrind 查找内存泄漏?【英文标题】:How do I use valgrind to find memory leaks? 【发布时间】:2011-07-05 07:52:32 【问题描述】:

如何使用 valgrind 查找程序中的内存泄漏?

请有人帮助我并描述执行该程序的步骤?

我使用的是 Ubuntu 10.04,我有一个程序 a.c,请帮帮我。

【问题讨论】:

你使用 valgrind 来测试你的编译程序,而不是源代码。 @RageD 下面给出的答案是正确的,你为什么不接受呢? 泄漏是由您未能做的某事引起的 - 即。释放分配的内存。因此,Valgrind 无法向您显示泄漏的“位置”——只有您知道不再需要分配的内存的位置。但是,通过告诉您哪个分配不是 free()d,通过在程序中跟踪该内存的使用情况,您应该能够确定它应该从哪里获得 free()d。一个常见的错误是错误退出函数而不释放分配的内存。 相关:使用任何工具:***.com/questions/6261201/… 【参考方案1】:

您可以在 .bashrc 文件中创建别名,如下所示

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 日志文件。

【讨论】:

【参考方案2】:

如何运行 Valgrind

不要侮辱 OP,但对于那些提出这个问题并且仍然是 Linux 新手的人——您可能必须在您的系统上安装 Valgrind

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind 很容易用于 C/C++ 代码,但甚至可以用于其他 正确配置的语言(请参阅this for Python)。

要运行 Valgrind,请将可执行文件作为参数传递(连同任何 程序的参数)。

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:在“完整”报告中显示所有“确定的、间接的、可能的、可达的”泄漏类型。 --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)

我们看一下我也写的C代码:

#include <stdlib.h>

int main() 
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;

嗯,丢失了 5 个字节。它怎么发生的?错误报告只是说 mainmalloc。在一个更大的程序中,这将是非常麻烦的 追捕。 这是因为可执行文件的编译方式。我们可以 实际上逐行详细了解出了什么问题。重新编译你的程序 带有调试标志(我在这里使用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

现在有了这个调试版本,Valgrind 指向确切的代码行 分配泄露的内存! (措辞很重要:它可能不会 正是您的泄漏在哪里,但是 what 被泄漏了。跟踪帮助您找到 在哪里。)

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)

调试内存泄漏和错误的技术

利用www.cplusplus.com!它有关于 C/C++ 函数的大量文档。 内存泄漏的一般建议: 确保您的动态分配的内存确实被释放了。 不要分配内存而忘记分配指针。 除非释放旧内存,否则不要用新指针覆盖指针。 内存错误的一般建议: 访问和写入您确定属于您的地址和索引。记忆 错误与泄漏不同;他们通常只是IndexOutOfBoundsException 类型问题。 释放内存后不要访问或写入内存。

有时您的泄漏/错误可能会相互关联,就像 IDE 发现您尚未键入右括号一样。解决一个问题可以解决其他问题,因此请寻找一个看起来是罪魁祸首的问题并应用以下一些想法:

列出代码中依赖/依赖于 具有内存错误的“违规”代码。跟随程序的执行 (甚至可能在gdb 中),并查找前置条件/​​后置条件错误。这个想法是跟踪程序的执行,同时关注分配内存的生命周期。 尝试注释掉“有问题的”代码块(在合理范围内,因此您的代码 仍然编译)。如果 Valgrind 错误消失,您就已经找到它的位置了。 如果一切都失败了,请尝试查找。 Valgrind 也有documentation!

查看常见的泄漏和错误

注意你的指针

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-&gt;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;

Valgrind 将我们指向上面的注释行。看这里的最后一次迭代, 这是*(destination + 26) = *(source + 26);。但是,*(source + 26) 是 再次超出范围,类似于无效写入。无效读也是一种 一个错误的常见结果。查看作业的右侧 操作。


开源 (U/Dys)topia

我怎么知道泄漏是我的?我在使用时如何找到我的泄漏 别人的代码?我发现了一个不属于我的泄漏;我应该做些什么吗?全部 是合法的问题。首先,2 个真实世界的例子,展示了 2 类 常见的遭遇。

Jansson: JSON 库

#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;

这是一个简单的程序:它读取 JSON 字符串并对其进行解析。在制作中, 我们使用库调用为我们进行解析。杨松做必要的事 动态分配,因为 JSON 可以包含自身的嵌套结构。 然而,这并不意味着我们decref 或“释放”从 每个功能。事实上,我上面写的这段代码同时抛出了“无效读取” 和“无效写入”。当您取出 decref 行时,这些错误就会消失 为value

为什么?变量 value 在 Jansson 中被认为是“借来的参考” API。 Jansson 会为您记录它的记忆,您只需 decref JSON 结构相互独立。这里的教训: 阅读文档。真的。有时很难理解,但 他们告诉你为什么会发生这些事情。相反,我们有 existing questions 关于这个内存错误。

SDL: 一个图形和游戏库

#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;

this code 有什么问题?它一直为我泄漏约 212 KiB 的内存。花点时间考虑一下。我们打开 SDL,然后关闭。回答?没有错。

That might sound bizarre at first。说实话,图形是杂乱无章的,有时您必须接受一些泄漏作为标准库的一部分。这里的教训是:你不需要平息每一次内存泄漏。有时您只需要suppress the leaks因为它们是您无能为力的已知问题。 (这不是我允许忽略你自己的泄漏!)

对虚空的回答

我怎么知道泄漏是我的? 它是。 (无论如何,99% 肯定)

当我使用其他人的代码时,如何找到我的漏洞? 很可能其他人已经找到了它。试试谷歌!如果失败,请使用我上面给你的技能。如果这失败了,并且您主要看到 API 调用并且很少看到您自己的堆栈跟踪,请参阅下一个问题。

我发现了一个不属于我的泄漏;我应该做些什么吗? 是的!大多数 API 都有报告错误和问题的方法。使用它们!帮助回馈您在项目中使用的工具!


进一步阅读

感谢您陪伴我这么久。我希望你已经学到了一些东西,因为我试图倾向于广泛的人来得出这个答案。我希望你在此过程中问过一些事情:C 的内存分配器是如何工作的?究竟什么是内存泄漏和内存错误?它们与段错误有何不同? Valgrind 是如何工作的?如果您有任何这些,请满足您的好奇心:

More about malloc, C's memory allocator Definition of a segmentation fault Definition of a memory leak Definition of a memory access error How does Valgrind work?

【讨论】:

更好的答案,可惜这不是公认的答案。 我认为做这样的事情是一个好习惯,我自己做了一些 我可以为这个答案加注星标并将其用作自己的未来参考吗?干得好! memcheck 工具是否默认启用? @abhiarora 是的。手册页告诉我们memcheck 是默认工具:--tool=&lt;toolname&gt; [default: memcheck]【参考方案3】:

试试这个:

valgrind --leak-check=full -v ./your_program

只要安装了 valgrind,它就会通过您的程序并告诉您出了什么问题。它可以为您提供可能发现泄漏的指针和大致位置。如果您遇到段错误,请尝试通过gdb 运行它。

【讨论】:

“your_program”是什么意思?这个是源码位置还是apk文件等应用名称? your_program == 可执行文件名称或您用来运行应用程序的任何命令。【参考方案4】:

你可以运行:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]

【讨论】:

以上是关于如何使用 valgrind 查找内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章

Valgrind - 打开 Windows 内存转储

如何在源代码中查找内存泄漏

如何修复 Valgrind 日志中的内存泄漏

如何结合使用thrust和valgrind来检测内存泄漏?

在 Apache httpd 和模块中搜索内存泄漏

如何创建内存转储和分析内存泄漏?