C getline内存泄漏不同的行为

bui*_*ui3 4 c valgrind memory-leaks 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;
}
Run Code Online (Sandbox Code Playgroud)

第二种情况

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

Sha*_*awn 7

第一个程序没问题。

第二个的问题来自缓冲区长度参数到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;
}
Run Code Online (Sandbox Code Playgroud)

size_t在使用它时传递一个指向最初初始化为 0的变量的指针。现有bufsize变量 inmain()看起来适合使用:

//...
while ((my_getline(lf_fd, &lf_buffer, &bufsize)) == 0)
// ...
Run Code Online (Sandbox Code Playgroud)

虽然它很容易解决,但您遇到的内存泄漏似乎是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 实现,它realloc()用于所有分配(realloc(NULL, x)相当于malloc(x)),因此不会导致原始代码泄漏。这并不理想,因为它会realloc()在每次使用时导致,而不是仅在缓冲区不够大以容纳当前行时(与上面的固定版本不同),但它可以工作。