【问题标题】:Still Reachable Leak detected by ValgrindValgrind 检测到仍然可达泄漏
【发布时间】:2011-04-19 22:16:16
【问题描述】:

本块中提到的所有函数都是库函数。如何纠正这种内存泄漏?

它列在“仍可访问”类别下。 (还有4个,非常相似,但大小不一)

 630 bytes in 1 blocks are still reachable in loss record 5 of 5
    at 0x4004F1B: calloc (vg_replace_malloc.c:418)
    by 0x931CD2: _dl_new_object (dl-object.c:52)
    by 0x92DD36: _dl_map_object_from_fd (dl-load.c:972)
    by 0x92EFB6: _dl_map_object (dl-load.c:2251)
    by 0x939F1B: dl_open_worker (dl-open.c:255)
    by 0x935965: _dl_catch_error (dl-error.c:178)
    by 0x9399C5: _dl_open (dl-open.c:584)
    by 0xA64E31: do_dlopen (dl-libc.c:86)
    by 0x935965: _dl_catch_error (dl-error.c:178)
    by 0xA64FF4: __libc_dlopen_mode (dl-libc.c:47)
    by 0xAE6086: pthread_cancel_init (unwind-forcedunwind.c:53)
    by 0xAE61FC: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)

Catch:一旦我运行我的程序,它就没有出现内存泄漏,但它在 Valgrind 输出中多了一行,这是以前不存在的:

丢弃 0x5296fa0-0x52af438 处的符号 在 /lib/libgcc_s-4.4.4-20100630.so.1 由于 munmap()

如果泄漏无法纠正,至少有人可以解释为什么 munmap() 行导致 Valgrind 报告 0“仍然可达”泄漏?

编辑:

这是一个最小的测试样本:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *runner(void *param) {
    /* some operations ... */
    pthread_exit(NULL);
}

int n;

int main(void) {

    int i;
    pthread_t *threadIdArray;

    n=10; /* for example */

    threadIdArray = malloc((n+n-1)*sizeof(pthread_t));  

    for(i=0;i<(n+n-1);i++) {
        if( pthread_create(&threadIdArray[i],NULL,runner,NULL) != 0 ) {
            printf("Couldn't create thread %d\n",i);
            exit(1);
        }
    }


    for(i=0;i<(n+n-1);i++) {
        pthread_join(threadIdArray[i],NULL);
    }

    free(threadIdArray);

    return(0);
}

运行:

valgrind -v --leak-check=full --show-reachable=yes ./a.out

【问题讨论】:

标签: c pthreads valgrind


【解决方案1】:

对于未来的读者,“仍然可以访问”可能意味着您忘记关闭文件之类的内容。虽然在最初的问题中似乎不是这样,但您应该始终确保您已经这样做了。

【讨论】:

  • Valgrind 只用--track-fds=yes 报告泄露的文件描述符。它们不会被报告为“仍可访问”。
  • 好吧,如果它是用fopen 打开的,那么泄漏将是FILE 结构。
【解决方案2】:

这里是“仍然可达”的正确解释:

“仍然可达”是分配给全局和静态局部变量的泄漏。因为 valgrind 跟踪全局变量和静态变量,所以它可以排除分配“一劳永逸”的内存分配。全局变量分配了一次分配并且从未重新分配该分配通常不是“泄漏”,因为它不会无限增长。严格意义上来说仍然是泄密,但通常可以忽略,除非你是迂腐的。

已分配分配但未释放的局部变量几乎总是泄漏。

这是一个例子

int foo(void)
{
    static char *working_buf = NULL;
    char *temp_buf;
    if (!working_buf) {
         working_buf = (char *) malloc(16 * 1024);
    }
    temp_buf = (char *) malloc(5 * 1024);

    ....
    ....
    ....

}

Valgrind 会将 working_buf 报告为“仍然可达 - 16k”,将 temp_buf 报告为“肯定丢失 - 5k”。

【讨论】:

    【解决方案3】:

    由于底部的 pthread 系列中有一些例程(但我不知道那个特定的例程),我猜你已经启动了一些线程作为可连接的线程,它已经终止了执行。

    该线程的退出状态信息一直可用,直到您调用pthread_join。因此,内存在程序终止时保存在丢失记录中,但仍然可以访问,因为您可以使用pthread_join 访问它。

    如果这个分析是正确的,要么分离地启动这些线程,要么在终止你的程序之前加入它们。

    编辑:我运行了您的示例程序(经过一些明显的更正后),但没有出现以下错误

    ==18933== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
    --18933-- 
    --18933-- used_suppression:      2 dl-hack3-cond-1
    --18933-- used_suppression:      2 glibc-2.5.x-on-SUSE-10.2-(PPC)-2a
    

    由于dl- 与您看到的大部分内容相似,我猜您看到了一个已知问题,该问题在valgrind 的抑制文件方面具有解决方案。也许你的系统不是最新的,或者你的发行版没有维护这些东西。 (我的是ubuntu 10.4,64位)

    【讨论】:

    • 我和你一样得到 0 个错误。请查看泄漏摘要以获取有关“泄漏”的信息。
    • @crypto:我不明白。你的意思是你和我有同样的压抑?
    • used_suppression: 14 dl-hack3-cond-1
    【解决方案4】:

    定义“内存泄漏”的方法不止一种。特别是,程序员普遍使用的“内存泄漏”有两个主要定义。

    “内存泄漏”的第一个常用定义是“内存已分配,但在程序终止之前没有随后释放”。然而,许多程序员(正确地)争辩说,符合此定义的某些类型的内存泄漏实际上不会造成任何类型的问题,因此不应将 true 视为“内存泄漏”。

    “内存泄漏”的一个更严格(也更有用)的定义是,“内存已分配,不能随后被释放,因为程序不再有任何指向已分配内存块的指针。”换句话说,您无法释放不再有任何指针的内存。因此,这样的内存是“内存泄漏”。 Valgrind 使用术语“内存泄漏”的这个更严格的定义。这是一种可能导致大量堆耗尽的泄漏类型,尤其是对于长生命周期的进程。

    Valgrind 的泄漏报告中的“仍然可以访问”类别是指仅符合“内存泄漏”的第一个定义的分配。这些块没有被释放,但它们本来可以被释放(如果程序员愿意的话),因为程序仍在跟踪指向这些内存块的指针。

    一般来说,无需担心“仍可访问”的块。它们不会造成 true 内存泄漏可能导致的那种问题。例如,“仍然可以访问”的块通常不会导致堆耗尽。这是因为这些块通常是一次性分配,在整个进程的生命周期中都会保留对这些块的引用。虽然您可以检查并确保您的程序释放所有分配的内存,但这样做通常没有实际好处,因为无论如何操作系统都会在进程终止后回收所有进程的内存。将此与 true 内存泄漏进行对比,如果不加以修复,如果运行时间足够长,可能会导致进程耗尽内存,或者只会导致进程消耗比必要更多的内存。

    可能唯一有助于确保所有分配都具有匹配的“空闲”的情况是,如果您的泄漏检测工具无法判断哪些块“仍然可访问”(但 Valgrind 可以做到这一点),或者您的操作系统没有回收所有终止进程的内存(Valgrind 已被移植来执行此操作的所有平台)。

    【讨论】:

    • 你能猜出 munmap() 正在做什么使“仍然可以到达”的块消失吗?
    • @crypto:可能是因为卸载共享对象而调用了munmap。共享对象使用的所有资源都可能在卸载之前被释放。这可以解释为什么“仍然可达”在munmap 案例中被释放。不过,我只是在这里推测。这里没有足够的信息可以肯定地说。
    • “仍然可访问”内存可以被视为内存泄漏的一种情况:假设您有一个哈希表,您在其中添加指向堆分配内存的指针作为值。如果您继续在表上插入新条目,但不会删除和释放您不再需要的条目,则它可能会无限增长,如果该内存“仍然可以访问”,则会泄漏堆内存事件。这就是您在 Java 或其他垃圾收集语言中可能遇到的内存泄漏的情况。
    • 另请参阅 valgrind 常见问题解答中有关 STL 创建的“仍然可访问”块的此答案。 valgrind.org/docs/manual/faq.html#faq.reports
    • “许多程序员(正确地)认为 [内存泄漏] 实际上并没有造成 [a] 问题,因此不应被视为真正的内存泄漏” - 大声笑。 .. 用这种内存泄漏构建一个本机 DLL,然后让 Java 或 .Net 使用它。 Java 和 .Net 在程序的生命周期中加载和卸载 DLL 数千次。每次重新加载 DLL 时,它都会泄漏更多内存。长时间运行的程序最终会耗尽内存。它让 Debian 的 OpenJDK 维护者发疯。当我们讨论 OpenSSL 的“良性”内存泄漏时,他在 OpenSSL 邮件列表中也说了同样的话。
    【解决方案5】:

    您似乎不明白still reachable 的含义。

    任何still reachable不是泄漏。你不需要做任何事情。

    【讨论】:

    • 这与 Valgrind 提供的其他措辞冲突并且在技术上不正确。内存在程序退出时“仍然可以访问”,因此可能存在泄漏。如果您正在调试代码以在程序退出后无法很好地清理内存的 RTOS 上运行怎么办?
    • 不幸的是,这并不总是正确的。例如,丢失的文件描述符可以算作内存泄漏,但 valgrind 将它们归类为“仍然可以访问”,大概是因为指向它们的指针仍然可以在系统表中访问。但是出于调试的目的,真正的诊断是“内存泄漏”。
    • 丢失的文件描述符是不是定义的内存泄漏。也许您在谈论丢失的FILE 指针?
    猜你喜欢
    • 2022-01-05
    • 1970-01-01
    • 2015-02-28
    • 2015-08-10
    • 2019-01-24
    • 1970-01-01
    • 2016-05-13
    相关资源
    最近更新 更多