【问题标题】:valgrind --trace-children=yes reports leak despite atexit cleanupvalgrind --trace-children=yes 报告泄漏尽管 atexit 清理
【发布时间】:2017-06-27 21:01:54
【问题描述】:

我试图避免 valgrind 的误报,但我很讨厌 atexit()fork() 的组合,尽管使用了 --trace-children=yes。我的代码:

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

static int * arr;

static void cleanup() {
    free(arr);
    printf("free arr as: %p\n", (void *)arr);
}

int main()
{
    arr = malloc(16 * sizeof(int));
    printf("allocated arr as: %p\n", (void *)arr);
    atexit(cleanup);

    pid_t pid = fork();
    if (pid == -1) {
        exit(1);
    } else if (pid == 0) {
        // child
        _exit(0);
    } else {
        // parent
        exit(0);
    }
}

命令行:

$ clang -Weverything leak.c 
$ valgrind --trace-children=yes ./a.out 
==3287== Memcheck, a memory error detector
==3287== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3287== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3287== Command: ./a.out
==3287== 
allocated arr as: 0x5202040
free arr as: 0x5202040
==3288== 
==3288== HEAP SUMMARY:
==3288==     in use at exit: 64 bytes in 1 blocks
==3288==   total heap usage: 2 allocs, 1 frees, 1,088 bytes allocated
==3288== 
==3288== LEAK SUMMARY:
==3288==    definitely lost: 0 bytes in 0 blocks
==3288==    indirectly lost: 0 bytes in 0 blocks
==3288==      possibly lost: 0 bytes in 0 blocks
==3288==    still reachable: 64 bytes in 1 blocks
==3288==         suppressed: 0 bytes in 0 blocks
==3288== Rerun with --leak-check=full to see details of leaked memory
==3288== 
==3288== For counts of detected and suppressed errors, rerun with: -v
==3288== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==3287== 
==3287== HEAP SUMMARY:
==3287==     in use at exit: 0 bytes in 0 blocks
==3287==   total heap usage: 2 allocs, 2 frees, 1,088 bytes allocated
==3287== 
==3287== All heap blocks were freed -- no leaks are possible
==3287== 
==3287== For counts of detected and suppressed errors, rerun with: -v
==3287== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

根据printf() 输出,看起来没有泄漏。我可以让 valgrind 相信这一点,还是应该将其添加到我的 valgrind 抑制文件中?

【问题讨论】:

    标签: c memory-leaks static valgrind atexit


    【解决方案1】:

    您正在子进程中使用 _exit()。根据 _exit 的手册页:

    The function _exit() is like exit(3), but does not call any functions registered with atexit(3) or on_exit(3). 
    

    将其更改为 exit(0)。它应该可以工作。

    【讨论】:

    • 谢谢!我看过其他关于fork() 的文档说孩子应该打电话给_exit() 而不是exit()[1]。但是,在// child 中手动调用cleanup() 似乎可行。 [1] “出于技术原因,此处必须使用 POSIX _exit 函数,而不是 C 标准的退出函数。)”en.wikipedia.org/wiki/Fork_(system_call)#Application_usage
    • @gperciva 您应该了解您所描述的建议背后的原因。在这种特殊情况下,首要的是避免调用已注册的退出处理程序,这在分叉的子进程中通常是可取的。如果您实际上想要调用已注册的退出处理程序,就像您在这种情况下所做的那样,您可能只想使用exit() 而不是_Exit(),正如这个答案所暗示的那样。
    • @gperciva,还要注意虽然_exit() 不是C 标准库的一部分,但标准库从C99 开始就有_Exit(),它做同样的事情并且更便携。
    • @gperciva,请参阅stackoverflow.com/questions/2329640/… 了解 exit() 与 _exit() 的用法。
    【解决方案2】:

    根据 printf() 输出,看起来没有泄漏。我可以让 valgrind 相信这一点,还是应该将其添加到我的 valgrind 抑制文件中?

    看来 valgrind 是对的。如果您将printf() 输出解释为没有泄漏,那么您就没有意识到fork() 的效果。

    当您派生一个子代时,它会获得其父代地址空间的完整副本。这通常通过写时复制页面来实现,但它仍然构成属于子进程的内存。在您的情况下,这包括动态分配数组 arr 的副本。

    子进程通过调用_exit() 退出,因此尽管它继承了其父进程的退出处理程序注册,但在该进程中不会调用已注册的退出处理程序。你可以说是这样,因为你只看到cleanup() 的输出一次。结果,属于孩子的arr 的副本永远不会被释放,正如 valgrind 告诉你的那样。

    不过,将其称为内存泄漏有点迂腐。当程序终止时,有问题的内存仍然可以访问,此时它会被系统回收。它只是在终止之前没有显式释放。

    【讨论】:

    • 谢谢!是的,我确实误解了fork() 的“其父母地址空间的副本”部分。为了帮助将来偶然发现这个问题的任何人,请谷歌“虚拟地址空间”和/或阅读:stackoverflow.com/a/5365635/7541781
    猜你喜欢
    • 2017-07-17
    • 1970-01-01
    • 2020-09-17
    • 1970-01-01
    • 2015-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多