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

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

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

最佳答案

如何运行 Valgrind

不要侮辱 OP,但是对于那些遇到这个问题并且仍然是 Linux 新手的人 - 你可能需要在你的系统上安装 Valgrind

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

Valgrind 很容易用于 C/C++ 代码,但甚至可以用于其他
正确配置时的语言(参见 this 了解 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 也有 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->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 中的“借用引用”
    应用程序接口(interface)。 Jansson 为您跟踪其内存,您只需 decrefJSON 结构彼此独立。这里的教训:
    阅读文档 。真的。有时很难理解,但
    他们告诉你为什么会发生这些事情。相反,我们有
    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?
  • 关于c - 如何使用 valgrind 查找内存泄漏?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5134891/

    10-16 22:55