自研C语言Shell出现strdup引发的内存泄漏问题求助
修复Shell实现中的strdup内存泄漏问题
我用C系统编程实现了一款支持最多20个管道(|)连续连接命令的Shell,采用父子进程处理。命令执行功能正常,但Valgrind检测显示存在由strdup引发的内存泄漏,即使调用了free操作也无法解决。
原代码
#include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/wait.h> #include <ctype.h> #include <stdlib.h> #include <time.h> #include <signal.h> #define MAX_LINE 1024 // The maximum length command #define MAX_COMMAND 20 // Maximum number of commands in a single line extern char* strdup(const char*); void log_child_process(pid_t pid, char *command); void execute_command(char *command, int in_fd, int out_fd) { pid_t pid = fork(); if (pid == 0) { // Child process if (in_fd != 0) { dup2(in_fd, 0); close(in_fd); } if (out_fd != 1) { dup2(out_fd, 1); close(out_fd); } log_child_process(getpid(), command); execl("/bin/sh", "sh", "-c", command, NULL); perror("execl"); exit(1); } else if (pid < 0) { perror("fork"); exit(1); } } void signal_handler(int sig) { if (sig == SIGINT) { printf("\nYou can not exit with CTRL+C pressed. ':q' to exit.\n"); printf("myshell> "); fflush(stdout); } else { printf("\nReceived signal %d, exiting.\n", sig); exit(0); } } void log_child_process(pid_t pid, char *command) { time_t rawtime; struct tm *timeinfo; char filename[80]; char *file_extension = ".txt"; time(&rawtime); timeinfo = localtime(&rawtime); strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S", timeinfo); strcat(filename, file_extension); FILE *file = fopen(filename, "a"); if (file == NULL) { perror("Error opening log file"); return; } fprintf(file, "PID: %d, Command: %s\n", pid, command); fclose(file); } char *strtrim(char *str) { char *end; // Trim leading space while (isspace(*str)) { str++; } if (*str == 0) { // All spaces? return str; } // Trim trailing space end = str + strlen(str) - 1; while (end > str && isspace(*end)) { end--; } *(end + 1) = '\0'; // Allocate new memory for trimmed string and copy it char *new_str = strdup(str); return new_str; } void split_commands(char *input, char **commands, int *num_commands) { int count = 0; const char delimiter[] = "|"; char *token; // Count the number of commands for (int i = 0; input[i] != '\0'; i++) { if (input[i] == '|') count++; } *num_commands = count + 1; // Split input into commands token = strtok(input, delimiter); for (int i = 0; i < *num_commands; i++) { commands[i] = strtrim(token); token = strtok(NULL, delimiter); } } void handle_redirections(char **command, int *in_fd, int *out_fd) { char *input_file = NULL; char *output_file = NULL; char *token; const char delimiter[] = " \t\n"; char *cmd = strdup(*command); char *new_command = (char *) calloc(MAX_LINE, sizeof(char)); token = strtok(cmd, delimiter); while (token != NULL) { if (strcmp(token, "<") == 0) { token = strtok(NULL, delimiter); input_file = strdup(token); } else if (strcmp(token, ">") == 0) { token = strtok(NULL, delimiter); output_file = strdup(token); } else { strcat(new_command, token); strcat(new_command, " "); } token = strtok(NULL, delimiter); } *command = strtrim(new_command); if (input_file != NULL) { *in_fd = open(input_file, O_RDONLY); if (*in_fd < 0) { perror(input_file); exit(1); } free(input_file); } if (output_file != NULL) { *out_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (*out_fd < 0) { perror(output_file); exit(1); } free(output_file); } free(cmd); free(new_command); } int main(int argc , char *argv[]) { if(argc > 1) { fprintf(stderr, "Usage: gcc -o program_name program_name.c && ./program_name\n"); exit(EXIT_FAILURE); } char input[MAX_LINE]; char *commands[MAX_COMMAND]; int num_commands; signal(SIGINT, signal_handler); while (1) { printf("myshell> "); fflush(stdout); if (fgets(input, MAX_LINE, stdin) == NULL) { break; } // Remove trailing newline character input[strlen(input) - 1] = '\0'; if (strcmp(input, ":q") == 0) { exit(0); } split_commands(input, commands, &num_commands); if(num_commands > MAX_COMMAND){ fprintf(stderr, "Error: Single line can have maximum %d commands.\n", MAX_COMMAND); exit(EXIT_FAILURE); } int in_fd = 0; int out_fd = 1; int fd[2]; for (int i = 0; i < num_commands; i++) { if (i < num_commands - 1) { if (pipe(fd) < 0) { perror("pipe"); exit(1); } out_fd = fd[1]; } else { out_fd = 1; } handle_redirections(&commands[i], &in_fd, &out_fd); execute_command(commands[i], in_fd, out_fd); if (in_fd != 0) { close(in_fd); } if (out_fd != 1) { close(out_fd); } in_fd = fd[0]; } for (int i = 0; i < num_commands; i++) { free(commands[i]); commands[i] = NULL; // added to avoid dangling pointer } for (int i = 0; i < num_commands; i++) { wait(NULL); } } return 0; }
Valgrind检测结果
Valgrind显示泄漏由strdup操作引发,具体为部分strdup分配的堆内存未被正确释放。
问题分析
- 指针覆盖导致内存泄漏:在
handle_redirections函数中,*command = strtrim(new_command);会用新的堆内存指针覆盖原来的*command指针(该指针来自split_commands中strtrim返回的strdup分配的内存),但原来的内存地址未被保存,无法执行free,造成泄漏。 - 栈内存被错误free:
strtrim函数中,当输入字符串全为空格时,直接返回原栈指针(指向input数组的栈内存),后续在main中free(commands[i])会尝试释放栈内存,属于未定义行为,同时也可能引发内存检测工具误报。
修复方案
1. 修复指针覆盖问题
在handle_redirections函数中,保存原来的*command指针,替换后立即释放旧内存:
void handle_redirections(char **command, int *in_fd, int *out_fd) { char *input_file = NULL; char *output_file = NULL; char *token; const char delimiter[] = " \t\n"; char *old_command = *command; // 保存旧指针 char *cmd = strdup(*command); char *new_command = (char *) calloc(MAX_LINE, sizeof(char)); token = strtok(cmd, delimiter); while (token != NULL) { if (strcmp(token, "<") == 0) { token = strtok(NULL, delimiter); input_file = strdup(token); } else if (strcmp(token, ">") == 0) { token = strtok(NULL, delimiter); output_file = strdup(token); } else { strcat(new_command, token); strcat(new_command, " "); } token = strtok(NULL, delimiter); } *command = strtrim(new_command); free(old_command); // 释放旧的堆内存 // 其余代码不变... }
2. 修复栈内存free问题
修改strtrim函数,确保无论输入是否全为空格,都返回strdup分配的堆内存:
char *strtrim(char *str) { char *end; // Trim leading space while (isspace((unsigned char)*str)) { str++; } if (*str == 0) { // All spaces? return strdup(""); // 返回空堆内存,而非栈指针 } // Trim trailing space end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) { end--; } *(end + 1) = '\0'; // Allocate new memory for trimmed string and copy it char *new_str = strdup(str); return new_str; }
验证
修复后重新用Valgrind检测,原有的strdup内存泄漏问题将被解决,同时也避免了栈内存被错误释放的未定义行为。
内容的提问来源于stack exchange,提问作者Lisandro Lopes
相关产品推荐
相关产品推荐

