C实现简易shell时getcwd内存泄漏 释放指针无效如何修复
简易shell getcwd 内存泄漏修复
编写简易shell时,除找不到可执行文件场景外其余功能均运行正常。Valgrind检测提示内存泄漏来自exe_find函数,泄漏点指向getcwd调用。
相关代码与检测信息
exe_find 函数实现
/** * exe_find - creates the path of an executable. * @exe: executable to search for. * * Return: a pointer to the argument to be given to execve function. */ char *exe_find(char *exe) { Node *head = linked_path(), *temp, *current; char *cwd = NULL, *exe_dir = NULL, *to_exe; int is_ok; temp = head; cwd = getcwd(cwd, 0); // this pointer is on the heap, but I can't seem to free it // when I free the pointer here it says no memory leaks possible // but that's illegal because a syscall still needs to point to this while (temp) { chdir(temp->key); is_ok = access(exe, F_OK); if (is_ok == -1) { current = temp; temp = temp->next; free(current->key); free(current); } if (!is_ok) { exe_dir = temp->next->key; break; } } if (!exe_dir) { return (NULL); } chdir(cwd); to_exe = malloc(sizeof(char) * (strlen(exe_dir) + strlen(exe) + 2)); if (!to_exe) return (NULL); strcpy(to_exe, exe_dir); strcat(to_exe, "/"); strcat(to_exe, exe); // otherwhise, I tried freeing exe, exe_dir and cwd, it doesn't do anything return (to_exe); }
Valgrind 检测输出
HEAP SUMMARY: ==19975== in use at exit: 13 bytes in 1 blocks ==19975== total heap usage: 30 allocs, 29 frees, 6,793 bytes allocated ==19975== ==19975== 13 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==19975== at 0x483AD7B: realloc (vg_replace_malloc.c:834) ==19975== by 0x4952EFF: getcwd (getcwd.c:84) ==19975== by 0x1098C8: exe_find (main-2.h:80) ==19975== by 0x109B58: displayAndRun (shell.c:75) ==19975== by 0x109CD1: get_prompt (shell.c:122) ==19975== by 0x109DBA: main (shell.c:154) ==19975== ==19975== LEAK SUMMARY: ==19975== definitely lost: 13 bytes in 1 blocks ==19975== indirectly lost: 0 bytes in 0 blocks ==19975== possibly lost: 0 bytes in 0 blocks ==19975== still reachable: 0 bytes in 0 blocks ==19975== suppressed: 0 bytes in 0 blocks
displayAndRun 函数实现
/** * displayAndRun - waits for a user to enter a command and executes it. * @line: pointer to store the address of the buffer containing the text. * @id: child process id. * @args: shell arguments. * * Return: nothing. */ void displayAndRun(char *line, pid_t id, char **args) { char *to_exe; if (isatty(STDIN_FILENO)) { if (id) { wait(NULL); printf("#cisfun$ "); } else { if (execve(args[0], args, NULL) == -1) { to_exe = exe_find(args[0]); if (!to_exe) execute(1, args, line); if (execve(to_exe, args, NULL) == -1) execute(1, args, line); } } } }
get_prompt 函数实现
/** * get_prompt - gets the command from the user. * @line: pointer to store the address of the buffer containing the text. * @n: address to size of line. * * Return: nothing. */ void get_prompt(char *line, size_t n) { ssize_t nread; pid_t id; char **split; extern char **environ; int i; while ((nread = getline(&line, &n, stdin)) != -1) { if (!strncmp(line, "exit", 4)) { free(line); exit(0); } split = strsplt(line, " \n", '\0'); id = fork(); if (id == -1) exit(98); if (!strncmp(line, "env", 3) && !id) { free(line); for (i = 0; environ[i]; i++) printf("%s\n", environ[i]); execute(0, split, NULL); exit(98); } displayAndRun(line, id, split); non_inter(line, split, id); execute(0, split, NULL); } free(line); }
main 函数实现
int main(int argc, char **argv) { char *line; size_t n; if (argc != 1) { printf("Usage: %s\n", argv[0]); return (1); } n = 0; line = NULL; if (!isatty(STDIN_FILENO)) printf("#cisfun$\n"); printf("#cisfun$ "); get_prompt(line, n); return (0); }
问题根因
- 对
getcwd的使用存在认知错误:当传入getcwd(NULL, 0)时,glibc会在堆上动态分配内存存储当前工作路径,这块内存必须由调用方手动释放。不存在“系统调用还持有指针不能释放”的情况——chdir(cwd)执行完成后,内核已经完成目录切换,不会再引用这个用户态内存指针。 exe_find存在两个提前返回分支,这两个分支中没有释放cwd:- 遍历PATH链表未找到匹配可执行文件时,直接
return NULL,已分配的cwd未释放 - 拼接最终路径时
malloc失败,直接return NULL,同样未释放cwd
- 遍历PATH链表未找到匹配可执行文件时,直接
- 额外隐藏问题:
- 遍历PATH链表找到可执行文件后直接break,剩余未遍历的链表节点内存没有释放,会造成额外泄漏
displayAndRun中如果execve调用失败返回,to_exe指针指向的内存没有释放,也会造成泄漏- 原逻辑中找到文件时取
temp->next->key存在空指针访问风险:如果匹配到的是链表最后一个节点,temp->next为NULL,直接取key会触发段错误
修复方案
1. 修正exe_find逻辑
优先去掉不必要的目录切换操作,直接拼接路径做access校验,从根源减少资源管理复杂度;所有返回路径前必须释放已分配的堆内存(包括cwd和PATH链表的所有节点)。
参考修正代码:
char *exe_find(char *exe) { Node *head = linked_path(), *temp, *current; char *cwd = NULL, *exe_dir = NULL, *to_exe; int is_ok; // 传入绝对路径直接处理,无需遍历PATH if (exe[0] == '/') { while (head) { current = head; head = head->next; free(current->key); free(current); } return strdup(exe); } cwd = getcwd(NULL, 0); temp = head; while (temp) { size_t tmp_len = strlen(temp->key) + strlen(exe) + 2; char *tmp_path = malloc(tmp_len); snprintf(tmp_path, tmp_len, "%s/%s", temp->key, exe); is_ok = access(tmp_path, F_OK); free(tmp_path); current = temp; temp = temp->next; if (is_ok == 0) { exe_dir = current->key; free(current); break; } free(current->key); free(current); } // 释放剩余未遍历的链表节点 while (temp) { current = temp; temp = temp->next; free(current->key); free(current); } // 切回原工作目录后立即释放cwd if (cwd) { chdir(cwd); free(cwd); } if (!exe_dir) { return NULL; } to_exe = malloc(strlen(exe_dir) + strlen(exe) + 2); if (!to_exe) { free(exe_dir); return NULL; } strcpy(to_exe, exe_dir); strcat(to_exe, "/"); strcat(to_exe, exe); free(exe_dir); return to_exe; }
2. 修正displayAndRun的内存释放逻辑
execve只有调用失败才会返回,此时必须释放to_exe再走后续错误处理逻辑:
if (execve(args[0], args, NULL) == -1) { to_exe = exe_find(args[0]); if (!to_exe) { execute(1, args, line); } if (execve(to_exe, args, NULL) == -1) { free(to_exe); // execve失败返回,必须释放 execute(1, args, line); } }
内容的提问来源于stack exchange,提问作者Marlon
相关产品推荐
相关产品推荐

