首页 > 解决方案 > C getline内存泄漏不同的行为

问题描述

我有一个关于函数的问题getline(),正如 报告的那样,在关于内存使用的两种情况下,它的行为似乎有所不同valgrind。我发布了两种情况的代码并解释了行为。我希望有人能指出我正确的方向。

第一个案例

getline()在 while 循环中调用,读取缓冲区中文本文件的所有行。然后缓冲区仅在循环结束时被释放一次:在这种情况下valgrind不会出现错误(不会发生泄漏)。

int main(int argc, char* argv[])
{
    char* buffer = NULL;
    size_t bufsize = 0;
    ssize_t nbytes;
    int counter = 0;
    char error = 0;

    FILE* input_fd = fopen(argv[1], "r");

    while ((nbytes = getline(&buffer, &bufsize, input_fd)) != -1)
    {
        counter += 1;
    }

    free(buffer);
    fclose(input_fd);

    return 0;
}

第二种情况

同一个循环调用一个函数,该函数又调用getline(),传递同一个缓冲区。同样,缓冲区仅在循环结束时被释放一次,但在这种情况下valgrind报告内存泄漏。事实上,让程序运行并查看 RSS,我可以看到它随着循环的进行而增加。请注意,在循环内添加一个空闲(每个循环释放缓冲区)问题就消失了。这是代码。

int my_getline(FILE* lf_fd, char** lf_buffer)
{
    ssize_t lf_nbytes = 0;
    size_t lf_bufsiz = 0;
    lf_nbytes = getline(lf_buffer, &lf_bufsiz, lf_fd);
    if (lf_nbytes == -1)
        return 1;
    return 0;
}

int main(int argc, char* argv[])
{
    char* lf_buffer = NULL;
    size_t bufsize = 0;
    ssize_t nbytes;
    int counter = 0;
    int new_line_counter = 0;
    char error = 0;

    FILE* lf_fd = fopen(argv[1], "r");

    while ((my_getline(lf_fd, &lf_buffer)) == 0)
    {
        // Added to allow measuring the RSS
        sleep(2);
   
        // If I uncomment this, no memory leak occurs
        //free(lf_buffer);
    }

    free(lf_buffer);
    fclose(lf_fd);

    return 0;
}

Valgrind 输出

==9604== Memcheck, a memory error detector
==9604== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9604== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==9604== Command: ./my_getline_x86 /media/sf_Scambio/processes.log
==9604== HEAP SUMMARY:
==9604==     in use at exit: 1,194 bytes in 2 blocks
==9604==   total heap usage: 8 allocs, 6 frees, 11,242 bytes allocated
==9604== 
==9604== 1,194 bytes in 2 blocks are definitely lost in loss record 1 of 1
==9604==    at 0x483DFAF: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-
linux.so)
==9604==    by 0x48E371D: getdelim (iogetdelim.c:102)
==9604==    by 0x1092B3: my_getline (my_getline.c:14)
==9604==    by 0x10956A: main (my_getline.c:38)
==9604== 
==9604== LEAK SUMMARY:
==9604==    definitely lost: 1,194 bytes in 2 blocks
==9604==    indirectly lost: 0 bytes in 0 blocks
==9604==      possibly lost: 0 bytes in 0 blocks
==9604==    still reachable: 0 bytes in 0 blocks
==9604==         suppressed: 0 bytes in 0 blocks
==9604== 
==9604== For lists of detected and suppressed errors, rerun with: -s
==9604== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

标签: cmemory-leaksvalgrindgetline

解决方案


第一个程序很好。

第二个问题来自缓冲区长度参数getline()。您my_getline()始终将其设置为 0,这意味着getline()每次都分配一个新缓冲区(至少,对于您正在使用的 glibc 实现;见下文)。将其更改为

int my_getline(FILE* lf_fd, char** lf_buffer, size_t* lf_bufsiz)
{
    ssize_t lf_nbytes = 0;
    lf_nbytes = getline(lf_buffer, lf_bufsiz, lf_fd);
    if (lf_nbytes == -1)
        return 1;
    return 0;
}

并在使用时将指针传递给size_t最初初始化为 0 的变量。中的现有bufsize变量main()看起来适合使用:

//...
while ((my_getline(lf_fd, &lf_buffer, &bufsize)) == 0)
// ...

虽然很容易解决,但您遇到的内存泄漏似乎是 glibc 实现中的一个错误getline()

POSIX 文档

如果*lineptr是空指针或如果 指向的对象*lineptr大小不足,则应按 分配malloc() 对象 或按 重新分配对象 ,realloc()使对象大到足以容纳要存储的字符写给它...

glibc 手册页

或者,在调用 之前getline()*lineptr可以包含一个指向 - 分配的malloc(3)缓冲区*n字节大小的指针。 如果缓冲区不够大,无法容纳该行,请根据需要使用、getline()realloc(3)更新*lineptr调整其大小*n

这些表明,如果您遇到的情况是,您将有效的非NULL指针传递给内存并说它的长度为 0,则应该使用该函数realloc()来调整它的大小。但是,glibc 实现会检查*lineptr == NULL || *n == 0,如果为真,则会用新分配的缓冲区覆盖*lineptr,从而导致您看到的泄漏。比较用于所有分配的NetBSD实现(等同于),因此不会导致原始代码泄漏。这并不理想,因为它在每次使用时都会导致 a,而不是仅在缓冲区不够大以容纳当前行时(与上面的固定版本不同),但它可以工作。realloc()realloc(NULL, x)malloc(x)realloc()


推荐阅读