C语言动态分配字符数组读文件出现额外异常字符问题排查
动态内存分配读单词代码故障排查
问题背景
- 对动态内存分配相关用法不熟悉,累计花费近6小时排查代码错误未定位根因
- 实现目标:从文件中读取以空格分隔的第一个单词,单词长度不固定;功能仅针对单行内容设计,不需要处理多行场景,后续需要扩展读取更多文件内容,因此必须使用动态内存分配实现
- 两周后参加C语言相关考试,动态内存分配是尚未完全掌握的必考知识点
问题代码
char ch; char* line=(char*)calloc(1, sizeof(char)); if(input!=NULL) { for(int num=1; (ch=fgetc(input)) != EOF; num++) //input为指向输入文件的指针 if(ch!=' ') { line=(char*)realloc(line, sizeof(char)*num+1); strcat(line, &ch); } else break; }
初始设计思路:先为行缓冲区
line分配1个字符的空间并初始化为0,之后每读取一个有效非空格字符,就通过realloc将缓冲区大小增加1个字符长度,遇到空格则停止读取第一个单词。
异常现象
- 保留for循环中的
num++逻辑时,程序直接崩溃 - 去掉
num++逻辑时,预期输出字符串Nole,实际输出乱码N☺o☺l☺e☺ - 打印输出内容的ASCII码结果为
78 1 111 1 108 1 101 1:每个有效字符后都跟着ASCII值为1的字符,该值和num的初始值完全一致;如果将num初始值改为2,后续跟随的异常字符ASCII值也会变为2,跨编译器、跨设备测试现象完全一致。
根因分析
代码存在4个核心错误,完全对应观察到的所有异常:
strcat用法严重错误strcat的第二个参数必须是指向以'\0'(ASCII值0)结尾的合法字符串的指针。传入的是单个字符变量ch的地址&ch,ch本身仅占1字节,没有后续的终止符,strcat会从ch的地址开始逐字节拷贝内存,直到碰到值为0的字节才停止。栈上ch相邻的位置刚好存储的是变量num,值为1(或设置的初始值2),因此每次拷贝都会把当前读到的字符、num的值、后续栈上的垃圾值一起拷入line,这就是每个有效字符后跟着和num值相同乱码的直接原因。- 缓冲区溢出触发堆损坏
写的分配大小sizeof(char)*num+1受运算符优先级影响,实际计算结果为(sizeof(char)*num) + 1。保留num++逻辑时,第一次读到字符num=1,仅分配2字节内存,但错误的strcat调用会写入至少3字节内容(当前字符+num值+终止符),直接越界覆盖堆内存的管理元数据,破坏堆结构,最终触发程序崩溃。去掉num++时,num恒为1,每次都只分配2字节,同样存在溢出,但因为内存布局巧合没有立刻触发崩溃,才会输出带乱码的结果。 fgetc返回值接收类型错误fgetc的返回值是int类型(取值范围为0-255的字符值+值为-1的EOF),用char类型接收会在char默认为无符号类型的环境下无法正确识别EOF,触发死循环。realloc使用不规范realloc分配失败时会返回NULL,直接将返回值赋值给原指针line,如果分配失败会丢失原内存地址,造成内存泄漏。
修复后参考代码
int ch; // 修正:用int类型接收fgetc返回值,正确识别EOF // 初始分配1字节存字符串终止符,初始有效字符长度为0 char* line = (char*)calloc(1, sizeof(char)); if (line == NULL) { // 内存分配失败的错误处理 exit(EXIT_FAILURE); } int current_len = 0; // 记录当前已读取的有效字符长度 if (input != NULL) { // 读到EOF、空格、换行时停止,符合单单词单行读取需求 while( (ch = fgetc(input)) != EOF && ch != ' ' && ch != '\n' ) { current_len++; // 用临时指针接收realloc返回值,避免分配失败丢失原指针 char* tmp = (char*)realloc(line, current_len + 1); // 分配大小=有效字符数+1字节终止符 if (tmp == NULL) { free(line); exit(EXIT_FAILURE); } line = tmp; line[current_len - 1] = (char)ch; // 直接给对应下标位置赋值,不要用strcat拼单个字符 line[current_len] = '\0'; // 手动补字符串终止符,保证字符串合法 } } // 此处使用line完成业务逻辑,例如puts(line); // 使用完成后必须释放内存,避免泄漏 // free(line);
考试核心知识点
- C语言中所有合法字符串必须以
'\0'作为终止符,strcat/strcpy/puts/printf("%s")等所有字符串操作函数,都依赖这个终止符判断字符串边界,没有终止符的字符数组不能当字符串使用 - 动态申请的内存绝对不能越界写入,否则会破坏堆的管理结构,触发崩溃或不可预期的行为,是动态内存考点的高频错题
realloc不要直接把返回值赋值给原指针,必须先用临时指针接收、判断分配成功后再赋值给原指针- 所有单个字符的地址不能直接作为字符串参数传给字符串处理函数,因为没有自带终止符
fgetc/getchar等字符读取函数的返回值是int类型,必须用int变量接收才能正确判断EOF
内容的提问来源于stack exchange,提问作者Luca
相关产品推荐
相关产品推荐

