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

有人能帮我描述一下手术的步骤吗?

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


试试这个:

Valgrind——leak-check=full -v ./your_program

只要安装了valgrind,它就会检查你的程序并告诉你哪里出了问题。它可以给你指针和可能发现漏洞的大致位置。如果您正在进行段错误,请尝试通过gdb运行它。


你可以运行:

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

如何运行Valgrind

不是要侮辱OP,但是对于那些遇到这个问题并且仍然是linux新手的人来说,您可能必须在您的系统上安装Valgrind。

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.
sudo pacman -Syu valgrind  # Arch, Manjaro, Garuda, 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: "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.

最后,你会希望看到这样的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)

我有个漏洞,但在哪里?

你有内存泄漏,而瓦尔grind没有说任何有意义的话。 也许,是这样的:

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个字节。这是怎么发生的?错误报告只是说 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

现在在这个调试构建中,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)

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

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 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!


查看常见的泄漏和错误

注意你的指针

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 -数组移动到的>数据。

无效的写

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],这就是为什么*(字母+ 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);。然而,*(源+ 26)是 再次出界,类似于无效写入。无效读取也是 离一错误的常见结果。看作业的右边 操作。


开源(U/Dys)的乌托邦

我怎么知道漏水的是我?我在使用时如何找到漏洞 别人的代码?我发现了一个不属于我的漏洞;我该做点什么吗?所有 都是合理的问题。首先,两个真实世界的例子,展示了两个类 共同的遭遇。

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可以包含自己的嵌套结构。 然而,这并不意味着我们减少或“释放”给我们的内存 每一个函数。事实上,我上面写的这段代码抛出了一个“无效读取” 和“无效写入”。当你去掉递减线,这些误差就消失了 的价值。

为什么?变量值在Jansson中被认为是一个“借来的引用” API。Jansson会为你记录它的内存,你只需要减少 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;
}

这段代码有什么问题?它始终为我泄漏~ 212kib的内存。花点时间思考一下。我们打开SDL,然后关闭。答案?没什么问题。

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

对虚空的回答

我怎么知道漏水的是我? 它是。(至少99%确定)

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

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


进一步的阅读

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

更多关于malloc, C语言的内存分配器 分段错误的定义 内存泄漏的定义 内存访问错误的定义 Valgrind是如何运作的?


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