使用C语言getline读取stdin时如何避免内存泄漏及无效释放问题
简易Shell中getline内存处理问题及修复
我用C语言编写简易Shell时,使用getline从stdin读取输入出现内存错误,尽管已经释放所有malloc分配的内存,问题仍存在。作为C语言新手,希望获取getline处理文件输入时的正确内存处理建议。
读取行的代码
char *readline(int *eof) { char *input = NULL; size_t bufsize = 0; *eof = getline(&input, &bufsize, stdin); return (input); }
主函数代码片段
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); }
Valgrind错误信息
执行 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)
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); }
核心问题分析
- 重复释放内存:
strtok直接在原字符串line上分割,返回的指针是line的子地址,并非独立分配的内存。主函数中先free(args[i])再free(line),相当于重复释放同一块内存,触发Valgrind的Invalid free()错误。 - getline返回值判断错误:主函数中
if (eof == -1)是错误的,eof是指针,应该判断*eof == -1;且getline返回-1时,可能已分配了部分内存,未释放会导致泄漏。 - tokenize扩容参数错误:
realloc时用position而非bufsize计算内存大小,会导致分配的内存不足,存在越界风险。 - tokenize错误处理冗余:
toks == NULL时调用free(toks)无效,NULL不需要释放。
修复步骤
1. 修正主函数的内存释放与返回值判断
移除对args[i]的释放(因为它们是line的一部分),修正getline返回值的判断逻辑:
while (status) { mode = isatty(STDIN_FILENO); if (mode != 0) { write(STDOUT_FILENO, "$ ", 3); } line = readline(&eof); // 修正:判断指针指向的返回值,而非指针本身 if (*eof == -1) { // 若getline失败但已分配内存,需释放 if (line != NULL) free(line); exit(EXIT_FAILURE); } args = tokenize(line); status = hsh_execute(args, env, argv[0]); // 仅释放args数组本身,不需要释放args[i] free(args); free(line); }
2. 修正tokenize函数的问题
移除冗余的free(toks),修正realloc的内存大小参数:
#define BUFSIZE 64 #define DELIM " \t\r\n\a" char **tokenize(char *line) { char **toks = malloc(sizeof(char *) * BUFSIZE); int position = 0; char *token, **token_backup; int bufsize = BUFSIZE; if (toks == NULL) { perror("hsh"); exit(EXIT_FAILURE); } token = strtok(line, DELIM); while (token != NULL) { toks[position] = token; position++; // 用bufsize判断是否需要扩容 if (position >= bufsize) { bufsize += BUFSIZE; token_backup = toks; // 修正:用bufsize计算总内存大小 toks = realloc(toks, bufsize * sizeof(char *)); if (toks == NULL) { free(token_backup); perror("allocation error"); exit(EXIT_FAILURE); } } token = strtok(NULL, DELIM); } toks[position] = NULL; return (toks); }
3. getline内存处理规范
- 调用
getline前,确保传入的指针为NULL(当前readline函数已满足); getline返回-1时,必须检查返回的指针是否非空,若已分配内存则释放;- 成功读取后,使用完毕必须释放
getline返回的指针。
内容的提问来源于stack exchange,提问作者New
相关产品推荐
相关产品推荐

