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字节数据,不会越界覆写栈内存,所以不会触发段错误。
代码中还存在两个隐藏问题:
- 每组密文切换逻辑中多余调用了一次
fscanf,会导致每组密文的第一个字节被跳过,读取内容错位 main函数的第二个参数类型不符合C标准,正确写法应为char* argv[](即char** argv),当前写法char* argv会引发命令行参数相关的未定义行为
修复方案
- 将接收
%X格式解析结果的变量改为unsigned int类型,匹配fscanf的参数要求,避免栈内存越界写入 - 删除密文组切换逻辑中多余的
fscanf调用,避免读取错位 - 修正
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
相关产品推荐
相关产品推荐

