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


180

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

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

我正在使用Ubuntu 10.04,并且我有一个程序a.c,请帮帮我。


16
您使用valgrind测试您的编译程序,而不是源代码。
托尼

6
@RageD在下面给出的答案是正确的,为什么不接受呢?
Pratik Singhal 2014年

1
泄漏是由您无法执行的操作引起的-即。可用的已分配内存。因此,Valgrind无法向您显示泄漏的“位置”-只有您知道不再需要分配的内存。但是,通过告诉您哪些分配不是free()d,通过在程序中跟踪该内存的使用,您应该能够确定应该在哪里获得free()d。一个常见的错误是在不释放分配的内存的情况下错误退出了一个函数。
MikeW '16

1
相关信息:使用任何工具:stackoverflow.com/questions/6261201/…–
西罗·桑蒂利

Answers:


293

如何运行Valgrind

并不是侮辱OP,而是针对那些遇到此问题并且对Linux还是陌生的人- 您可能必须在系统上安装Valgrind

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

Valgrind是为C / C ++代码容易使用的,但是当正确配置(参见甚至可以用于其它语言为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指向 分配泄漏内存的确切代码行!(用词是很重要的:它可能不是正是你的泄漏,但什么得到了泄露的跟踪可以帮助你找到。 哪里。)

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也有文档

看常见的泄漏和错误

注意你的指针

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

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


开源(U / Dys)拓扑

我怎么知道泄漏是我的?使用其他人的代码时如何查找泄漏?我发现不是我的泄漏;我应该做点什么吗?都是合法的问题。首先,有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字符串并进行解析。在制作过程中,我们使用库调用为我们进行解析。Jansson动态地进行必要的分配,因为JSON可以包含其自身的嵌套结构。但是,这并不意味着我们decref或“释放”每个函数给我们的内存。实际上,我上面编写的这段代码同时抛出“无效读取”和“无效写入”。当您取出decref生产线时,这些错误消失了value

为什么?value在Jansson API中,该变量被视为“借用参考”。Jansson会为您跟踪其内存,您只需要decref 彼此独立的JSON结构即可。此处的课程: 阅读文档。真。有时很难理解,但是它们告诉您为什么发生这些事情。相反,我们对这个内存错误有 疑问

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

此代码有什么问题?它始终为我泄漏〜212 KiB的内存。花一点时间考虑一下。我们打开然后关闭SDL。回答?没有任何错误。

起初听起来可能很奇怪。说实话,图形是混乱的,有时您必须接受一些泄漏作为标准库的一部分。这里的教训:您不必平息每个内存泄漏。有时您只需要抑制泄漏 因为它们是已知问题,您无能为力。(这不是我忽略自己泄漏的权限!)

回答虚无

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

使用其他人的代码时如何查找泄漏?
可能已经有人发现了它。试试谷歌!如果失败,请使用我上面提供的技能。如果失败,并且您大多数情况下会看到API调用并且几乎没有自己的堆栈跟踪,请参阅下一个问题。

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


进一步阅读

谢谢你陪我这么久 我希望您学到了一些东西,因为我尝试着吸引各种各样的人获得这个答案。我希望您在此过程中已经提出了一些问题:C的内存分配器如何工作?什么是内存泄漏和内存错误?它们与段错误有何不同?Valgrind如何工作?如果您有以下任何一种,请提供您的好奇心:


4
更好的答案,可惜这不是公认的答案。
A. Smoliak

我相信这样做是一种好习惯,我做了一些自己的事
A. Smoliak

1
我可以注视这个答案并将其用作自己的未来参考吗?干得好!
Zap

没有memcheck工具是默认启用?
abhiarora

@abhiarora是的。手册页告诉我们这memcheck是默认工具:--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

试试这个:

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

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


“ your_program”是什么意思?这是源代码位置还是应用程序名称,例如apk文件?
HoangVu

7
your_program==可执行文件名称或用于运行应用程序的任何命令。
RageD


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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.