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

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个核心错误,完全对应观察到的所有异常:

  1. strcat用法严重错误
    strcat的第二个参数必须是指向以'\0'(ASCII值0)结尾的合法字符串的指针。传入的是单个字符变量ch的地址&ch,ch本身仅占1字节,没有后续的终止符,strcat会从ch的地址开始逐字节拷贝内存,直到碰到值为0的字节才停止。栈上ch相邻的位置刚好存储的是变量num,值为1(或设置的初始值2),因此每次拷贝都会把当前读到的字符、num的值、后续栈上的垃圾值一起拷入line,这就是每个有效字符后跟着和num值相同乱码的直接原因。
  2. 缓冲区溢出触发堆损坏
    写的分配大小sizeof(char)*num+1受运算符优先级影响,实际计算结果为(sizeof(char)*num) + 1。保留num++逻辑时,第一次读到字符num=1,仅分配2字节内存,但错误的strcat调用会写入至少3字节内容(当前字符+num值+终止符),直接越界覆盖堆内存的管理元数据,破坏堆结构,最终触发程序崩溃。去掉num++时,num恒为1,每次都只分配2字节,同样存在溢出,但因为内存布局巧合没有立刻触发崩溃,才会输出带乱码的结果。
  3. fgetc返回值接收类型错误
    fgetc的返回值是int类型(取值范围为0-255的字符值+值为-1的EOF),用char类型接收会在char默认为无符号类型的环境下无法正确识别EOF,触发死循环。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:09:20