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

Shell模拟C程序内存泄漏定位求助:Valgrind检测到未释放内存且存在重复释放问题

解决Shell模拟程序中的内存泄漏与重复释放问题

让我们一步步拆解你遇到的问题,然后给出针对性的修复方案:

核心问题分析

从你的代码和Valgrind日志来看,主要存在三个关键问题:

1. 越界赋值导致内存管理混乱

在初始化writableUsersArgs数组后,你执行了:

writableUsersArgs[index] = NULL;

这里的index在循环结束后的值是numberArgs + 1,但writableUsersArgs的长度仅为numberArgs + 1(下标范围是0到numberArgs),这属于越界写入,会破坏数组外的内存,同时导致原本应该设为NULL的writableUsersArgs[numberArgs]仍然是malloc分配的指针,后续释放逻辑也因此混乱。

2. 重复释放与不必要的NULL指针释放

在父进程的释放循环中:

for(index = 0; index < numberArgs + 1; index++) {
    free(usersArgs[index]);
    free(writableUsersArgs[index]);
}
  • usersArgs[numberArgs]是commandDelimeter设置的NULL指针,free NULL本身是安全的,但属于多余操作;
  • writableUsersArgs[numberArgs]如果被正确设为NULL,free它也是多余的,甚至可能因为之前的越界赋值导致指针丢失,引发潜在的重复释放。

3. 字符串内存分配未考虑结束符

你在mallocwritableUsersArgs元素时,只分配了maxArgLength*sizeof(char),但strcpy需要额外的1字节存储字符串结束符\0,这会导致缓冲区溢出,间接引发内存管理问题。

修复后的完整代码

int start(char* command){ 
    // pid uses status's value behind the scenes 
    int status; 
    int maxArgLength = 0; 
    int argLength = 0; 
    int index; 
    // Split the user's command into seperate strings wherever the command has spaces 
    char** usersArgs = commandDelimeter(command); 
    int numberArgs = countArgs(command); 
    // Calculate maxArgLength to allocate correct amount of memory 
    for(index = 0; index < numberArgs; index++) { 
        argLength = countLongestArg(usersArgs[index]); 
        if(argLength > maxArgLength) { 
            maxArgLength = argLength; 
        } 
    } 
    // Allocate memory for the array that gets passed to exec() function 
    char* writableUsersArgs[numberArgs+1]; 
    for(index = 0; index < numberArgs; index++) { 
        // 额外+1存储字符串结束符\0
        writableUsersArgs[index] = (char*)malloc((maxArgLength + 1)*sizeof(char)); 
    } 
    // Copy the strings from the read only array to a writable array of strings 
    for(index = 0; index < numberArgs; index++) { 
        strcpy(writableUsersArgs[index], usersArgs[index]); 
    } 
    // 直接指定下标设置NULL,避免依赖循环后的index值
    writableUsersArgs[numberArgs] = NULL; 
    // Create a child process 
    pid_t pid = fork(); 
    if(pid == -1) { 
        printf("Error forking"); 
        // fork失败时先释放已分配的内存,避免泄漏
        for(index = 0; index < numberArgs; index++) {
            free(writableUsersArgs[index]);
        }
        for(index = 0; index < numberArgs; index++) {
            free(usersArgs[index]);
        }
        free(usersArgs);
        return -1; 
    } else if(pid == 0) { 
        // Jumps into new child process 
        execv(writableUsersArgs[0], writableUsersArgs); 
        // execv失败时打印错误并退出子进程(用exit而非return更可靠)
        perror("execv failed");
        exit(EXIT_FAILURE);
    } else { 
        // Waits for child process to terminate before proceeding 
        wait(&status); 
        // 释放usersArgs的实际参数元素,再释放数组本身
        for(index = 0; index < numberArgs; index++) { 
            free(usersArgs[index]); 
        } 
        free(usersArgs); 
        // 释放writableUsersArgs的实际参数元素(最后一个是NULL无需释放)
        for(index = 0; index < numberArgs; index++) { 
            free(writableUsersArgs[index]); 
        } 
        return 1; 
    } 
}

关键修复点说明

  1. 修正NULL赋值逻辑:直接通过writableUsersArgs[numberArgs] = NULL设置数组末尾的NULL,避免越界。
  2. 调整释放循环范围:只释放usersArgs和writableUsersArgs中实际分配内存的前numberArgs个元素,跳过末尾的NULL指针,消除重复释放风险。
  3. 字符串内存分配加1:确保有足够空间存储\0,避免缓冲区溢出。
  4. fork失败时的内存清理:在fork出错时立即释放已分配的内存,防止泄漏。
  5. execv失败后的子进程退出:用exit(EXIT_FAILURE)替代return,确保子进程正确终止。

验证修复

重新用Valgrind运行程序,你会发现两个明确的内存泄漏都被消除,重复释放的问题也不再出现。如果还有其他问题,可以检查commandDelimeter和countArgs函数的一致性,确保两者返回的参数数目完全匹配。

内容的提问来源于stack exchange,提问作者Korey Lombardi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 22:52:40