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; } }
关键修复点说明
- 修正NULL赋值逻辑:直接通过
writableUsersArgs[numberArgs] = NULL设置数组末尾的NULL,避免越界。 - 调整释放循环范围:只释放
usersArgs和writableUsersArgs中实际分配内存的前numberArgs个元素,跳过末尾的NULL指针,消除重复释放风险。 - 字符串内存分配加1:确保有足够空间存储
\0,避免缓冲区溢出。 - fork失败时的内存清理:在fork出错时立即释放已分配的内存,防止泄漏。
- execv失败后的子进程退出:用
exit(EXIT_FAILURE)替代return,确保子进程正确终止。
验证修复
重新用Valgrind运行程序,你会发现两个明确的内存泄漏都被消除,重复释放的问题也不再出现。如果还有其他问题,可以检查commandDelimeter和countArgs函数的一致性,确保两者返回的参数数目完全匹配。
内容的提问来源于stack exchange,提问作者Korey Lombardi
相关产品推荐
相关产品推荐

