处理29个文件后C语言代码崩溃问题求助
命令行文件重命名工具崩溃问题排查与修复
问题概述
基于C语言开发的命令行文件重命名工具,在Arch Linux环境用gcc 12.2.0编译,处理少量文件时功能正常,但每次处理到第29个文件时程序崩溃。工具预期将文件按「前缀+序号+原扩展名」格式重命名,例如bg.jpeg→file-1.jpeg。
问题代码
renameFiles函数代码
#include <stdio.h> #include <dirent.h> #include <string.h> #include <stdlib.h> int renameFiles(char *files[], char *path, char *prefix) { int i = 0; FILE *f; char *filename; char *ext; char *buffer; long numbytes; while ( files[i] != NULL ){ filename = files[i]; char filepath[sizeof path + sizeof filename]; strcpy(filepath, path); strcat(filepath, filename); f = fopen(filepath, "rb"); if (f == NULL) return 1; fseek(f, 0L, SEEK_END); numbytes = ftell(f); fseek(f, 0L, SEEK_SET); buffer = (char*)calloc(numbytes, sizeof(char*)); if (buffer == NULL) return 1; fread(buffer, sizeof(char), numbytes, f); fclose(f); remove(filepath); ext = strrchr(filename, '.'); if (!ext) ext = ""; char int_str[sizeof i]; sprintf(int_str, "%d", i + 1); char newfilename[sizeof prefix + sizeof int_str + sizeof ext]; strcpy(newfilename, prefix); strcat(newfilename, int_str); strcat(newfilename, ext); char newfilepath[sizeof path + sizeof newfilename]; strcpy(newfilepath, path); strcat(newfilepath, newfilename); f = fopen(newfilepath, "w"); if (f == NULL) return 1; fwrite(buffer, 1, numbytes, f); printf("%s\n", newfilepath); fclose(f); i++; } return 0; }
Main.c代码
#include "files.h" #include <readline/readline.h> #include <readline/history.h> int main(int argc, char *argv[]) { char *files[1024] = {}; getFilesList(files, argv[1]); int length = 0; while( files[length] != NULL ){ length++; } printf("%d files found on %s\n", length + 1, argv[1]); char *userInput = readline("Continue (y/n): "); if (strcmp(userInput, "y") != 0){ return 0; } renameFiles(files, argv[1], argv[2]); return 0; }
崩溃原因分析
缓冲区溢出(核心原因)
代码中多次用sizeof(指针)计算字符串数组大小,比如char filepath[sizeof path + sizeof filename];。这里path和filename是指针类型,sizeof返回的是指针自身的字节数(64位系统为8字节),而非字符串的实际长度。这导致数组容量远小于实际需求,字符串拼接时会破坏栈内存。当处理到第29个文件时,序号为30(字符串长度2),加上前缀、扩展名后总长度超过了过小的数组容量,触发崩溃。内存分配错误
buffer = (char*)calloc(numbytes, sizeof(char*));中,sizeof(char*)是指针大小(8字节),但buffer是char类型数组,应使用sizeof(char)。此错误会导致分配的内存是实际需要的8倍,造成内存浪费,但不是直接崩溃原因。低效且危险的文件操作
代码通过「读取原文件内容→删除原文件→写入新文件」的方式实现重命名,完全没必要。这种方式不仅效率低下,还会在处理大文件时占用大量内存,同时增加IO出错风险。内存泄漏
每次循环中用calloc分配的buffer未释放,会导致内存泄漏,但不会直接引发崩溃。
修复方案
核心修复点
- 用
strlen()获取字符串实际长度,或定义足够大的静态缓冲区(如#define MAX_PATH 4096,符合Linux路径长度限制)。 - 替换错误的内存分配参数为
sizeof(char)。 - 用系统调用
rename()直接重命名文件,替代读写文件的低效方式。 - 释放动态分配的内存,避免泄漏。
修正后的renameFiles函数
#include <stdio.h> #include <dirent.h> #include <string.h> #include <stdlib.h> #include <errno.h> #define MAX_PATH 4096 // Linux系统路径最大长度一般不超过4096字节 int renameFiles(char *files[], char *path, char *prefix) { int i = 0; char *filename; char *ext; char filepath[MAX_PATH]; char newfilepath[MAX_PATH]; char int_str[10]; // 足够存储整数转为字符串的长度(支持到10位数字) while (files[i] != NULL) { filename = files[i]; // 安全拼接原文件路径 snprintf(filepath, sizeof(filepath), "%s%s", path, filename); // 获取文件扩展名 ext = strrchr(filename, '.'); if (!ext) ext = ""; // 生成序号字符串 snprintf(int_str, sizeof(int_str), "%d", i + 1); // 安全拼接新文件路径 snprintf(newfilepath, sizeof(newfilepath), "%s%s%s%s", path, prefix, int_str, ext); // 调用系统函数直接重命名 if (rename(filepath, newfilepath) != 0) { fprintf(stderr, "重命名失败: %s\n", strerror(errno)); return 1; } printf("%s\n", newfilepath); i++; } return 0; }
额外优化
- 使用
snprintf()替代strcpy()+strcat(),从根源避免缓冲区溢出。 - 移除不必要的文件读写逻辑,大幅提升效率,同时消除内存分配相关问题。
- 增加错误信息输出,便于排查重命名失败的具体原因。
其他注意事项
- 主函数中文件数量统计存在错误:
printf("%d files found on %s\n", length + 1, argv[1]);应改为printf("%d files found on %s\n", length, argv[1]);,因为length已经是遍历到NULL时的有效文件计数。 - 确保
getFilesList()函数正确遍历目录,返回的文件名列表以NULL结尾,且不会超出files数组的容量(1024)。
内容的提问来源于stack exchange,提问作者Naapuri
相关产品推荐
相关产品推荐

