You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题根因

  1. 对getcwd的使用存在认知错误:当传入getcwd(NULL, 0)时,glibc会在堆上动态分配内存存储当前工作路径,这块内存必须由调用方手动释放。不存在“系统调用还持有指针不能释放”的情况——chdir(cwd)执行完成后,内核已经完成目录切换,不会再引用这个用户态内存指针。
  2. exe_find存在两个提前返回分支,这两个分支中没有释放cwd:
    • 遍历PATH链表未找到匹配可执行文件时,直接return NULL,已分配的cwd未释放
    • 拼接最终路径时malloc失败,直接return NULL,同样未释放cwd
  3. 额外隐藏问题:
    • 遍历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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 09:51:30