在C语言中使用getline从stdin读取时如何避免内存泄漏?

How to avoid memory leaks when using getline to read from stdin in C?

提问人:New 提问时间:2/25/2023 最后编辑:New 更新时间:2/25/2023 访问量:189

问:

在 C 中使用 getline 从 stdin 读取时,我遇到了内存泄漏,尽管确保所有 malloc 都已释放,但在构建简单的 shell 时问题仍然存在。作为 C 语言的新手,我正在寻找有关在使用 getline 进行文件输入时如何正确处理内存的建议。

这是我用来读取该行的代码

char *readline(int *eof)
{
    char *input = NULL;
    size_t bufsize = 0;
    *eof = getline(&input, &bufsize, stdin);
    return (input);
}

这是实际的 main 函数。


    while (status)
    {
        mode = isatty(STDIN_FILENO);
        if (mode != 0)
        {
            write(STDOUT_FILENO, "$ ", 3);
        }
        line = readline(&eof);
        if (eof == -1)
        {
            exit(EXIT_FAILURE);
        }
        args = tokenize(line);
        status = hsh_execute(args, env, argv[0]);
        i = 0;
        while(args[i] != NULL)
        {
            free(args[i]);
            i++;
        }
        free(args);
        free(line);
    }

这是我运行命令 echo “/bin/ls” | ./shell 时 valgrind 返回的错误

==33899== Invalid free() / delete / delete[] / realloc()
==33899==    at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==33899==    by 0x10980D: main (shell.c:40)
==33899==  Address 0x4a96040 is 0 bytes inside a block of size 120 free'd
==33899==    at 0x484B27F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==33899==    by 0x1097D5: main (shell.c:36)
==33899==  Block was alloc'd at
==33899==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==33899==    by 0x48EB1A2: getdelim (iogetdelim.c:62)
==33899==    by 0x1096CA: readline (readline.c:12)
==33899==    by 0x10976B: main (shell.c:26)
==33899== 
==33899== 
==33899== HEAP SUMMARY:
==33899==     in use at exit: 120 bytes in 1 blocks
==33899==   total heap usage: 4 allocs, 4 frees, 4,848 bytes allocated
==33899== 
==33899== LEAK SUMMARY:
==33899==    definitely lost: 0 bytes in 0 blocks
==33899==    indirectly lost: 0 bytes in 0 blocks
==33899==      possibly lost: 0 bytes in 0 blocks
==33899==    still reachable: 120 bytes in 1 blocks
==33899==         suppressed: 0 bytes in 0 blocks
==33899== Rerun with --leak-check=full to see details of leaked memory
==33899== 
==33899== For lists of detected and suppressed errors, rerun with: -s
==33899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

关于 realloc 问题,这就是我在 tokenize 函数中处理它的方式

#define BUFSIZE 64
#define DELIM " \t\r\n\a"
char **tokenize(char *line)
{
    /*declaration of various fucntions*/
    char **toks = malloc(sizeof(char *) * BUFSIZE);
    int position = 0;
    char *token, **token_backup;
    int bufsize = BUFSIZE;

    /*allocation error*/
    if (toks == NULL)
    {
        free(toks);
        perror("hsh");
    }
    token = strtok(line, DELIM);
    /*Store  token in toks*/
    while (token != NULL)
    {
        toks[position] = token;
        position++;
        /*not enough memory*/
        if (position >= BUFSIZE)
        {
            bufsize += BUFSIZE;
            token_backup = toks;
            toks = realloc(toks, position * sizeof(char *));
            if (toks == NULL)
            {
                free(token_backup);
                printf("allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
        token = strtok(NULL, DELIM);
    }
    toks[position] = NULL;
    return (toks);
}
c 内存泄漏 malloc

评论

0赞 user3386109 2/25/2023
与 相关的代码和相应的代码看起来还行。您确实有很多其他内存分配正在进行。你为什么认为它被泄露了?getlinefreeline
0赞 New 2/25/2023
我编辑了它并添加了 valgrind 错误。我在 tokenize 函数中执行了 realloc,这会导致内存泄漏吗?
0赞 pm100 2/25/2023
我的猜测,tokenize 使用 strtok 并因此返回指向线的指针数组
2赞 user3386109 2/25/2023
我认为我们需要看到一个最小的可重复的例子
2赞 pm100 2/25/2023
请在 tokenize 中显示所有代码

答:

1赞 Necklondon 2/25/2023 #1

我猜那条线

char *readline(int *eof)

应该是

char *readline(ssize_t *eof)

并应相应地声明。eof

评论

1赞 Necklondon 2/25/2023
@user3386109 是的,你是对的!
0赞 chqrlie 2/25/2023 #2

该函数不会在它返回的已分配数组中分配单个字符串。这解释了如何释放两次:何时和结束时。此外,如果该行包含多个单词或以空格开头,则使用指向已分配块内部的指针进行调用,该指针具有未定义的行为。tokenizelinefree(args[i])i0free(line)mainfree

该函数也存在问题:tokenize

  • 当您重新分配数组时,您传递了错误的大小:而不是您应该传递 .position * sizeof(char *)bufsize * sizeof(char *)

这是修改后的版本:

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

/* getline wrapper with a different API */
ssize_t readline(char **linep) {
    size_t bufsize = 0;
    *linep = NULL;
    return = getline(linep, &bufsize, stdin);
}

#define BUFSIZE 64
#define DELIM " \t\r\n\a"
char **tokenize(char *line) {
    /* declaration of various variables */
    int bufsize = BUFSIZE;
    int position = 0;
    char **toks = malloc(sizeof(char *) * bufsize);
    char *token, **token_backup;

    if (toks == NULL) {
        /* allocation error */
        perror("cannot allocate array");
        exit(EXIT_FAILURE);
    }
    /* break line into tokens and store them in toks */
    token = strtok(line, DELIM);
    while (token != NULL) {
        toks[position] = token;
        position++;
        if (position >= BUFSIZE) {
            /* need more space */
            bufsize += BUFSIZE;
            token_backup = toks;
            toks = realloc(toks, sizeof(char *) * bufsize);
            if (toks == NULL) {
                free(token_backup);
                perror("cannot reallocate array");
                exit(EXIT_FAILURE);
            }
        }
        token = strtok(NULL, DELIM);
    }
    toks[position] = NULL;
    return toks;
}

char **env = NULL;
int hsh_execute(char **args, char **env, char *command);

int main(void) {
    int status = 1;

    while (status) {
        char *line;
        char **args;

        if (isatty(STDIN_FILENO)) {
            write(STDOUT_FILENO, "$ ", 3);
        }
        if (readline(&line) == -1) {
            // end of file reached
            exit(EXIT_FAILURE);
        }
        args = tokenize(line);
        status = hsh_execute(args, env, argv[0]);
        free(args);
        free(line);
    }
    return 0;
}
2赞 dbush 2/25/2023 #3

您没有内存泄漏。您正在释放已释放的内存。

    i = 0;
    while(args[i] != NULL)
    {
        free(args[i]);
        i++;
    }
    free(args);
    free(line);

每个都是指向 内部某处的指针。它们不是单独分配的。args[i]line

所以摆脱循环,只有自由和.argsline

评论

0赞 New 2/25/2023
正如你所说,我摆脱了循环,但我仍然丢失了 120 个字节。Valgrind 一直说 120 字节仍然可以访问。这正常吗
0赞 dbush 2/25/2023
这是你没有向我们展示的其他一些分配。运行 valgrind,它会告诉你来源。--leak-check=full --show-reachable=yes
0赞 New 2/25/2023
我第一次运行它并得到相同的消息,除了 4 个 frees 更改为 3.此外,lauch 函数只是调用没有分配的 execve--leak-check=full --show-leak-kinds=all
1赞 pm100 2/25/2023 #4

我的猜测是正确的,您正在使用 strtok。strtok 将返回指向输入缓冲区的指针。所以和 一样。所以你实际上最终删除了两次argv[0]lineline