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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5134891/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 07:55:03  来源:igfitidea点击:

How do I use valgrind to find memory leaks?

cvalgrind

提问by user484457

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

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

Please someone help me and describe the steps to carryout the procedure?

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

I am using Ubuntu 10.04 and I have a program a.c, please help me out.

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

回答by Joshua Detwiler

How to Run Valgrind

如何运行 Valgrind

Not to insult the OP, but for those who come to this question and are still new to Linux—you might have to install Valgrindon your system.

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

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

Valgrind is readily usable for C/C++ code, but can even be used for other languages when configured properly (see thisfor Python).

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

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

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

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.
  • --leak-check=full:“每个单独的泄漏都将详细显示”
  • --show-leak-kinds=all:在“完整”报告中显示所有“确定的、间接的、可能的、可达的”泄漏类型。
  • --track-origins=yes:比速度更喜欢有用的输出。这会跟踪未初始化值的来源,这对于内存错误非常有用。如果 Valgrind 慢得令人无法接受,请考虑关闭。
  • --verbose: 可以告诉你你的程序的异常行为。重复更多细节。
  • --log-file: 写入文件。当输出超过终端空间时很有用。

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

最后,您希望看到如下所示的 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)


I have a leak, but WHERE?

我有泄漏,但在哪里

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

所以,你有一个内存泄漏,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)

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

我们也来看看我写的C代码:

#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 mainand 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 gcchere):

好吧,丢失了 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

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

现在有了这个调试版本,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)


Techniques for Debugging Memory Leaks & Errors

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

  • Make use of www.cplusplus.com! It has great documentation on C/C++ functions.
  • General advice for memory leaks:
    • 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 IndexOutOfBoundsExceptiontype 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 gdbperhaps), 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 documentationtoo!
  • 使用www.cplusplus.com!它有关于 C/C++ 函数的大量文档。
  • 内存泄漏的一般建议:
    • 确保动态分配的内存确实被释放了。
    • 不要分配内存而忘记分配指针。
    • 除非旧内存被释放,否则不要用新指针覆盖指针。
  • 内存错误的一般建议:
    • 访问和写入您确定属于您的地址和索引。内存错误不同于泄漏;他们通常只是IndexOutOfBoundsException类型问题。
    • 释放内存后不要访问或写入内存。
  • 有时,您的泄漏/错误可以相互关联,就像 IDE 发现您尚未键入结束括号一样。解决一个问题可以解决其他问题,所以寻找一个看起来很好的罪魁祸首并应用以下一些想法:

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


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 realloccan actually move the allocated memory somewhere else and change the pointer's location. We then leave resizeArraywithout telling array->datawhere the array was moved to.

作为助教,我经常看到这个错误。学生使用局部变量而忘记更新原始指针。这里的错误是注意到realloc实际上可以将分配的内存移动到其他地方并更改指针的位置。然后我们离开resizeArray而不告诉 array->data阵列被移动到哪里。

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) = '
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)
'; //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.

请注意,Valgrind 将我们指向上面注释的代码行。大小为 26 的数组索引为 [0,25],这就是*(alphabet + 26)无效写入的原因——它越界了。无效写入是一对一错误的常见结果。查看赋值操作的左侧。

Invalid read

无效读取

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

And the code:

和代码:

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

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.

Valgrind 将我们指向上面的注释行。看看这里的最后一次迭代,即
*(destination + 26) = *(source + 26);. 但是,*(source + 26)再次越界,类似于无效写入。无效读取也是一对一错误的常见结果。查看赋值操作的右侧。



The Open Source (U/Dys)topia

开源 (U/Dys) 乌托邦

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.

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

Jansson: a JSON library

Jansson:一个 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;
}

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 decrefor "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 decrefline for value.

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

Why? The variable valueis considered a "borrowed reference" in the Jansson API. Jansson keeps track of its memory for you, and you simply have to decrefJSON 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 questionsabout this memory error.

为什么?该变量value在 Jansson API 中被视为“借用引用”。Jansson 会为您跟踪其内存,您只需要将decrefJSON 结构相互独立即可。这里的教训: 阅读文档。真的。有时很难理解,但他们会告诉您为什么会发生这些事情。相反,我们 有关于此内存错误的现有问题

SDL: a graphics and gaming library

SDL:图形和游戏库

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

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.

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

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 leaksbecause 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)

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

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.

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

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!

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



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:

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

回答by RageD

Try this:

尝试这个:

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

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.

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

回答by Rajat Paliwal

You can run:

你可以运行:

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

回答by Sachin Rastogi

You can create an alias in .bashrc file as follows

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

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

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

所以每当你想检查内存泄漏时,只需简单地做

##代码##

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

这将在当前目录中生成一个 Valgrind 日志文件。