仍可访问的内存泄漏被Valgrind检测到。

181

这个区块中提到的所有函数都是库函数。我该如何纠正这个内存泄漏问题?

它被列在“仍可达”类别下。(还有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)
捕捉: 我运行程序后,Valgrind输出中没有内存泄漏,但是多了一行信息:

在/lib/libgcc_s-4.4.4-20100630.so.1中,位于0x5296fa0-0x52af438之间的符号被丢弃 因为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

Valgrind FAQ - jww
5个回答

470

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

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

另一种可能更严格(也更有用)的“内存泄漏”定义是:“分配了内存,但由于程序不再具有对分配的内存块的指针,无法随后释放它。”换句话说,您不能释放您没有任何指针指向的内存。这种内存因此是“内存泄漏”。Valgrind使用该术语“内存泄漏”的较严格定义。这是一种可能会导致严重堆耗尽的泄漏,特别是对于长期运行的进程。

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

通常,无需担心“仍然可到达”的块。它们不会造成真正内存泄漏可能引起的问题。例如,“仍然可到达”的块通常没有导致堆用尽的潜在问题。这是因为这些块通常只分配一次,在整个进程的生命周期内都保持对其的引用。虽然您可以检查您的程序是否释放所有已分配的内存,但通常没有实际好处,因为操作系统会在进程终止后回收所有进程的内存。相比之下,真正的内存泄漏,如果不修复,可能会在长时间运行后使进程耗尽内存,或者仅仅会使进程消耗比必要更多的内存。

如果您的泄漏检测工具无法确定哪些块是“仍然可达”的(但Valgrind可以做到这一点),或者您的操作系统无法回收终止进程的所有内存(Valgrind已经移植到的所有平台都可以做到这一点),那么确保所有分配都有匹配的“释放”可能是唯一有用的时候。


3
可能是由于卸载共享对象导致了munmap的调用。在卸载之前,所有被共享对象使用的资源可能都会被释放。这可以解释为什么“仍然可达”的资源在munmap情况下被释放。不过这只是我的猜测,在这里没有足够的信息可以确定。 - Dan Moulding
3
“仍然可达”内存被视为内存泄漏的一种情况:假设您有一个哈希表,其中添加指向堆分配内存的指针作为值。如果您不断在表格上插入新条目,但不会删除和释放那些不再需要的条目,它可以无限增长,即使该内存在技术上仍然“可达”。这是Java或其他垃圾收集语言中可能出现的内存泄漏情况。 - lvella
1
请参考Valgrind FAQ中关于STL创建的“仍然可达”块的答案。http://valgrind.org/docs/manual/faq.html#faq.reports - John Perry
8
许多程序员(正确地)认为[泄漏的内存]实际上并不构成[一个]问题,因此不应被视为真正的内存泄漏。开玩笑......创建一个具有此类内存泄漏的本机DLL,并让Java或.Net使用它。 Java和.Net在程序生命周期中加载和卸载DLL数千次。每次重新加载DLL时,它都会泄漏更多的内存。长时间运行的程序最终会耗尽内存。这令Debian的OpenJDK维护人员疯狂。他在 OpenSSL邮件列表上也说了同样的话,当时我们正在讨论OpenSSL的“良性”内存泄漏。 - jww
Eclipse CDT(在Linux上)的氧气(或者是Doxygen?)在空闲几个小时后会消耗5GB内存,这是否与这种良性泄漏有关?另外,当我使用一个只有shared_ptr成员指向vector的简单C++类时,仍然会出现相同的“仍然可达”错误。如果智能指针仍然可达,则C++仍然可达。它说它是C ++。 - huseyin tugrul buyukisik
显示剩余4条评论

11

由于底层使用了来自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位)


我和你一样,没有收到任何错误。请检查“泄漏”摘要以获取相关信息。 - user191776
@crypto:我不明白。你的意思是你有和我一样的抑制吗? - Jens Gustedt
使用的抑制: 14 dl-hack3-cond-1 <- 这就是我的结果 - user191776

7
这里是对“仍然可达”一词的适当解释:
“仍然可达”是指分配给全局变量和静态局部变量的内存泄漏。因为valgrind跟踪全局和静态变量,它可以排除那些只被分配一次并且被遗忘的内存分配。一个全局变量只被分配一次并且从未重新分配该内存分配通常不会在无限增长的意义上被称为“泄漏”。从严格意义上讲,它仍然是一个泄漏,但通常可以忽略,除非你很追求完美。
几乎所有分配了内存却没有释放的局部变量都是泄漏。
以下是一个例子:
int foo(void)
{
    static char *working_buf = NULL;
    char *temp_buf;
    if (!working_buf) {
         working_buf = malloc(16 * 1024);
    }
    temp_buf = malloc(5 * 1024);
    ....
    ....
    ....
}

Valgrind会报告working_buf为"仍然可达 - 16k",temp_buf为"明确丢失 - 5k"。

2

对于未来读者,“仍然可达”可能意味着您忘记关闭某些内容,比如文件。虽然在原始问题中似乎不是这样,但您应始终确保已完成此操作。


Valgrind 只有在使用 --track-fds=yes 选项时才会报告泄漏的文件描述符,它们不会被报告为“仍然可达”。 - ZachB
2
如果使用fopen打开,那么泄漏将是针对FILE结构体。 - syockit

1

您似乎不理解“仍然可达(still reachable)”的含义。

任何“仍然可达(still reachable)”的内容都不是泄漏。您无需对此采取任何措施。


28
这与Valgrind提供的其他词汇不符,而且从技术上讲是不正确的。程序退出时,内存仍然可访问,因此可能会造成泄漏。如果您正在调试要在RTOS上运行的代码,而该系统在程序退出后没有很好地清理内存,那该怎么办? - Toymakerii
4
不幸的是,这并不总是正确的。例如,丢失的文件描述符可能会被视为内存泄漏,但是Valgrind将它们分类为“仍然可访问”,这可能是因为指向它们的指针仍然可以在系统表中访问。但是对于调试目的而言,真正的诊断结果是“内存泄漏”。 - Cyan
丢失的文件描述符根据定义不是内存泄漏。也许你说的是丢失的FILE指针? - Employed Russian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接