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

发布于 2024-10-19 14:31:54 字数 84 浏览 3 评论 0原文

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

我正在使用 Ubuntu 10.04,并且我有一个程序 ac

How do I use valgrind to find the memory leaks in a program?

I am using Ubuntu 10.04 and I have a program a.c.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

时光与爱终年不遇 2024-10-26 14:31:55

你可以运行:

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

You can run:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
护你周全 2024-10-26 14:31:55

您可以在 .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 日志文件。

You can create an alias in .bashrc file as follows

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

So whenever you want to check memory leaks, just do simply

vg ./<name of your executable> <command line parameters to your executable>

This will generate a Valgrind log file in the current directory.

你怎么这么可爱啊 2024-10-26 14:31:54

如何运行 Valgrind

首先检查您是否安装了 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 很容易用于 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)

-ggdb3 在二进制文件中包含详细的调试注释,Valgrind 可以使用它为您提供更精确的信息。以这种方式编译,GCC仍将使用默认的优化级别(-O2),这将改变您的代码以提高其效率。有时,这可能会优化您想要仔细探索的代码中的段落,或者产生其他令人惊讶的结果。在这些情况下,您可能想尝试使用 -Og 标志,该标志将包含详细的调试注释并且仅使用受限制的、调试友好的优化,以便编译的二进制文件将更接近你写的内容。您可以这样做:

gcc -o executable -std=c11 -Wall -Og main.c  # add -Og

您可以在 3.10 选项中阅读有关这些和其他调试选项的信息用于在 GCC 文档中调试您的程序。有关 -Og 如何影响优化的详细信息,请参阅 3.11控制优化的选项


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

  • 利用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;
}

Valgrind 将我们指向上面的注释行。看看这里的最后一次迭代,

*(目的地+26)=*(源+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 字符串并解析它。在制作过程中,
我们使用库调用来为我们进行解析。詹森做出了必要的
动态分配,因为 JSON 可以包含其自身的嵌套结构。
然而,这并不意味着我们decref或“释放”给我们的内存
每个功能。事实上,我上面写的这段代码会抛出“无效读取”
和“无效写入”。当您删除 decref 行时,这些错误就会消失
对于

为什么?变量 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 是如何工作的?如果您有这些,请满足您的好奇心:

How to Run Valgrind

First of all check you have Valgrind installed, if not:

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 is readily usable for C/C++ code, but can even be used for other
languages when configured properly (see this for Python).

To run Valgrind, pass the executable as an argument (along with any
parameters to the program).

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

The flags are, in short:

  • --leak-check=full: "each individual leak will be shown in detail"
  • --show-leak-kinds=all: Show all of "definite, indirect, possible, reachable" leak kinds in the "full" report.
  • --track-origins=yes: Favor useful output over speed. This tracks the origins of uninitialized values, which could be very useful for memory errors. Consider turning off if Valgrind is unacceptably slow.
  • --verbose: Can tell you about unusual behavior of your program. Repeat for more verbosity.
  • --log-file: Write to a file. Useful when output exceeds terminal space.

Finally, you would like to see a Valgrind report that looks like this:

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)

I have a leak, but WHERE?

So, you have a memory leak, and Valgrind isn't saying anything meaningful.
Perhaps, something like this:

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)

Let's take a look at the C code I wrote too:

#include <stdlib.h>

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

Well, there were 5 bytes lost. How did it happen? The error report just says
main and malloc. In a larger program, that would be seriously troublesome to
hunt down. This is because of how the executable was compiled. We can
actually get line-by-line details on what went wrong. Recompile your program
with a debug flag (I'm using gcc here):

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

Now with this debug build, Valgrind points to the exact line of code
allocating the memory that got leaked! (The wording is important: it might not
be exactly where your leak is, but what got leaked. The trace helps you find
where.)

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)

-ggdb3 includes detailed debug annotations in the binary that Valgrind can use to give you more precise information. Compiled this way, GCC will still use the default optimization level (-O2), which will transform your code to improve its efficiency. Sometimes that might optimize away passages in your code you want to explore closely, or have other surprising results. In those cases, you might want to try the flag -Og instead, which will include detailed debug annotations and use only restrained, debugging-friendly optimizations so that the compiled binary will more closely match what you wrote. You can do that like this:

gcc -o executable -std=c11 -Wall -Og main.c  # add -Og

You can read about these and other debugging options in 3.10 Options for Debugging Your Program in the GCC docs. For detailed information on how -Og affects optimization, see 3.11 Options That Control Optimization.


Techniques for Debugging Memory Leaks & Errors

  • Make use of cppreference! It has great documentation on C and C++ functions. Also consider www.cplusplus.com.

  • General advice for memory leaks:

  • If you can, use RAII and most of your problems will just go away.

  • Make sure your dynamically allocated memory does in fact get freed.

  • Don't allocate memory and forget to assign the pointer.

  • Don't overwrite a pointer with a new one unless the old memory is freed.

  • General advice for memory errors:

  • Access and write to addresses and indices you're sure belong to you. Memory
    errors are different from leaks; they're often just IndexOutOfBoundsException
    type problems.

  • Don't access or write to memory after freeing it.

  • Sometimes your leaks/errors can be linked to one another, much like an IDE discovering that you haven't typed a closing bracket yet. Resolving one issue can resolve others, so look for one that looks a good culprit and apply some of these ideas:

  • List out the functions in your code that depend on/are dependent on the
    "offending" code that has the memory error. Follow the program's execution
    (maybe even in gdb perhaps), and look for precondition/postcondition errors. The idea is to trace your program's execution while focusing on the lifetime of allocated memory.

  • Try commenting out the "offending" block of code (within reason, so your code
    still compiles). If the Valgrind error goes away, you've found where it is.

  • If all else fails, try looking it up. Valgrind has documentation too!


A Look at Common Leaks and Errors

Watch your pointers

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)

And the code:

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

As a teaching assistant, I've seen this mistake often. The student makes use of
a local variable and forgets to update the original pointer. The error here is
noticing that realloc can actually move the allocated memory somewhere else
and change the pointer's location. We then leave resizeArray without telling
array->data where the array was moved to.

Invalid write

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)

And the code:

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

Notice that Valgrind points us to the commented line of code above. The array
of size 26 is indexed [0,25] which is why *(alphabet + 26) is an invalid
write—it's out of bounds. An invalid write is a common result of
off-by-one errors. Look at the left side of your assignment operation.

Invalid read

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)

And the code:

#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 points us to the commented line above. Look at the last iteration here,
which is
*(destination + 26) = *(source + 26);. However, *(source + 26) is
out of bounds again, similarly to the invalid write. Invalid reads are also a
common result of off-by-one errors. Look at the right side of your assignment
operation.


The Open Source (U/Dys)topia

How do I know when the leak is mine? How do I find my leak when I'm using
someone else's code? I found a leak that isn't mine; should I do something? All
are legitimate questions. First, 2 real-world examples that show 2 classes of
common encounters.

Jansson: a JSON library

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

This is a simple program: it reads a JSON string and parses it. In the making,
we use library calls to do the parsing for us. Jansson makes the necessary
allocations dynamically since JSON can contain nested structures of itself.
However, this doesn't mean we decref or "free" the memory given to us from
every function. In fact, this code I wrote above throws both an "Invalid read"
and an "Invalid write". Those errors go away when you take out the decref line
for value.

Why? The variable value is considered a "borrowed reference" in the Jansson
API. Jansson keeps track of its memory for you, and you simply have to decref
JSON structures independent of each other. The lesson here:
read the documentation. Really. It's sometimes hard to understand, but
they're telling you why these things happen. Instead, we have
existing questions about this memory error.

SDL: a graphics and gaming library

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

What's wrong with this code? It consistently leaks ~212 KiB of memory for me. Take a moment to think about it. We turn SDL on and then off. Answer? There is nothing wrong.

That might sound bizarre at first. Truth be told, graphics are messy and sometimes you have to accept some leaks as being part of the standard library. The lesson here: you need not quell every memory leak. Sometimes you just need to suppress the leaks because they're known issues you can't do anything about. (This is not my permission to ignore your own leaks!)

Answers unto the void

How do I know when the leak is mine?
It is. (99% sure, anyway)

How do I find my leak when I'm using someone else's code?
Chances are someone else already found it. Try Google! If that fails, use the skills I gave you above. If that fails and you mostly see API calls and little of your own stack trace, see the next question.

I found a leak that isn't mine; should I do something?
Yes! Most APIs have ways to report bugs and issues. Use them! Help give back to the tools you're using in your project!


Further Reading

Thanks for staying with me this long. I hope you've learned something, as I tried to tend to the broad spectrum of people arriving at this answer. Some things I hope you've asked along the way: How does C's memory allocator work? What actually is a memory leak and a memory error? How are they different from segfaults? How does Valgrind work? If you had any of these, please do feed your curiousity:

北方的巷 2024-10-26 14:31:54

试试这个:

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

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

Try this:

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

As long as valgrind is installed it will go through your program and tell you what's wrong. It can give you pointers and approximate places where your leaks may be found. If you're segfault'ing, try running it through gdb.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文