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

C语言fscanf格式符从%c改为%02X后触发段错误排查

fscanf读取十六进制密文时触发段错误问题排查

问题背景

完成课程作业时需要从文件读取多组密文,初始实现的密文读取函数代码如下:

void get_ciphertexts_from_file(unsigned char** ciphertexts){
    unsigned char ch;
    FILE *fpIn;
    int i = 0;
    int cur_text = 0;
    int r = 0;
   
    fpIn = fopen("ctexts.txt", "r");

    while(fscanf(fpIn, "%02X", &ch) != EOF) {
        printf("mk3 %d %d %02x\n", cur_text, i, ch);
        ciphertexts[cur_text][i] = ch;  

        i++;
        if(i == 31) {
            cur_text++;

            if(cur_text >= 7) {
                break;
            }

            i = 0;
            fscanf(fpIn, "%02X", &ch);
        }

    }

    fclose(fpIn);
}

故障现象

代码运行时输出mk3 0 0 BB后立即触发段错误,初步定位异常由循环内第二次调用fscanf触发。存在反常现象:若将fscanf的格式符从"%02X"修改为"%c",代码即可正常运行无段错误,暂未明确该现象的成因。

已确认调试信息

  • 第一次fscanf调用返回值为1,符合读取成功的预期返回值
  • 循环内的if判断结果为假,if块内部代码未执行
  • 待读取文件的前两个字符为"BB",文件打开、初始读取逻辑正常
  • 即使注释掉while循环内的密文数组赋值语句、调试printf语句,段错误仍会触发,可排除这两处代码导致异常的可能

初步猜测问题与ch变量相关但未找到编写错误,也怀疑存在其他位置的内存损坏,动态内存分配逻辑简单,其余相关代码如下:

unsigned char** allocate_ciphertexts_mem() {
    unsigned char** ciphertexts = malloc(7 * sizeof(unsigned char*));

    for(int i = 0; i < 7; i++) {
        ciphertexts[i] = malloc(31);
    }

    return ciphertexts;
}

void free_ciphertexts_mem(unsigned char** ciphertexts){

    for(int i = 0; i < 7; i++) {
        free(ciphertexts[i]);

    }

    free(ciphertexts);

}

int main(int argc, char* argv) {
    unsigned char** ciphertexts = allocate_ciphertexts_mem();

    get_ciphertexts_from_file(ciphertexts);

    for(int text = 0; text < 7; text++) {
        for(int i = 0; i < 31; i++) {
            printf("%02x", ciphertexts[text][i]);
        }

        printf("\n");
    }


    free_ciphertexts_mem(ciphertexts);

    return 0;

}

问题根因

核心错误是fscanf的%X格式符要求传入的参数类型为指向unsigned int的指针,但代码中传入的是unsigned char*类型的&ch。
fscanf解析完十六进制值后,会按照unsigned int的长度(主流x86/ARM环境下为4字节)向传入的地址写入数据,但ch本身只占1字节,这会直接覆写ch所在栈位置相邻的3字节内存——栈上ch相邻位置刚好存的是文件指针fpIn的值,第一次fscanf执行完就把fpIn踩成了非法地址,第二次调用fscanf传入被损坏的文件指针时就直接触发段错误。
换成%c格式符时,fscanf只会写入1字节数据,不会越界覆写栈内存,所以不会触发段错误。
代码中还存在两个隐藏问题:

  1. 每组密文切换逻辑中多余调用了一次fscanf,会导致每组密文的第一个字节被跳过,读取内容错位
  2. main函数的第二个参数类型不符合C标准,正确写法应为char* argv[](即char** argv),当前写法char* argv会引发命令行参数相关的未定义行为

修复方案

  1. 将接收%X格式解析结果的变量改为unsigned int类型,匹配fscanf的参数要求,避免栈内存越界写入
  2. 删除密文组切换逻辑中多余的fscanf调用,避免读取错位
  3. 修正main函数参数定义,建议增加文件打开失败的异常判断
    修复后的读取函数参考:
void get_ciphertexts_from_file(unsigned char** ciphertexts){
    unsigned int ch; // 类型改为unsigned int,匹配%X格式要求
    FILE *fpIn;
    int i = 0;
    int cur_text = 0;
   
    fpIn = fopen("ctexts.txt", "r");
    if (fpIn == NULL) {
        perror("Open ctexts.txt failed");
        return;
    }

    while(fscanf(fpIn, "%02X", &ch) != EOF) {
        printf("mk3 %d %d %02x\n", cur_text, i, ch);
        ciphertexts[cur_text][i] = (unsigned char)ch;  

        i++;
        if(i == 31) {
            cur_text++;
            if(cur_text >= 7) {
                break;
            }
            i = 0;
            // 删除此处多余的fscanf调用
        }
    }

    fclose(fpIn);
}

修正后的main函数定义:

int main(int argc, char* argv[]) {
    // 原有业务逻辑保持不变
}

内容的提问来源于stack exchange,提问作者Dyskord

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.15 16:15:59