未知二进制文件数据量时fread与fwrite的正确用法
问题根因
- 统计文件内结构体数量的while循环执行完成后,文件内部读写指针已经移动到文件末尾,此时直接调用
fread无法读取到任何有效数据。malloc申请到的堆内存未被写入有效值,Windows Debug模式下堆内存默认填充0xCC值,会被误识别为有效数据:0xCCCCCCCC按32位有符号int解析结果为-842150451,连续0xCC存入字符数组就会显示为乱码。 ReadBinaryFile函数内硬编码打开文件名为cars.dat,未使用传入的file_name形参,传入其他文件名时会打开错误目标。- 逐块读取计数的方式IO次数多、效率低,且很容易遗漏文件指针重置步骤,不适用于固定大小结构体存储的二进制文件读取场景。
正确实现方案
对于存储固定大小结构体的二进制文件,不需要逐块循环计数,直接通过文件长度计算元素总数即可,步骤如下:
- 以二进制读模式打开目标文件
- 调用
fseek将文件指针移动到文件末尾,通过ftell获取文件总字节数 - 校验总字节数是否为单个结构体大小的整数倍,避免读取损坏的格式错误文件
- 总字节数除以单个结构体占用内存大小,即可得到文件内存储的结构体总数量
- 调用
rewind将文件指针重置到文件起始位置 - 申请对应大小的内存空间,一次性读取所有结构体数据
- 关闭文件句柄,返回读取到的数组指针和元素数量
修复后的完整核心代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct Cars { int years; char company[16]; } Tcars; void WriteBinaryFile(char* file_name, Tcars* p_1) { FILE* f = fopen(file_name, "wb"); // 实际使用请补充f==NULL的空指针校验 fwrite(p_1, sizeof(Tcars), 3, f); fclose(f); } Tcars* ReadBinaryFile(char* file_name, int* pc) { FILE* f = fopen(file_name, "rb"); *pc = 0; // 实际使用请补充f==NULL的空指针校验 // 移动指针到文件末尾获取总文件长度 fseek(f, 0, SEEK_END); long file_total_size = ftell(f); if (file_total_size < 0 || file_total_size % sizeof(Tcars) != 0) { // 文件损坏或格式不匹配的错误处理 fclose(f); return NULL; } // 计算文件内存储的结构体总数 *pc = file_total_size / sizeof(Tcars); if (*pc == 0) { fclose(f); return NULL; } // 将文件指针重置到文件起始位置 rewind(f); // 申请对应大小内存一次性读取全部数据 Tcars* p_2 = malloc(sizeof(Tcars) * (*pc)); // 实际使用请补充malloc返回NULL的空指针校验 size_t read_count = fread(p_2, sizeof(Tcars), *pc, f); if (read_count != *pc) { // 读取失败的错误处理 free(p_2); fclose(f); *pc = 0; return NULL; } fclose(f); return p_2; } int main() { Tcars* ParkingLot1 = malloc(sizeof(Tcars) * 3); for(int i=0;i<3;i++) { ParkingLot1[i].years = 2000 + i; } strcpy(ParkingLot1[0].company, "Fiat"); strcpy(ParkingLot1[1].company, "Ford"); strcpy(ParkingLot1[2].company,"Toyota"); Tcars* ParkingLot2 = NULL; int cars_amount = 0; WriteBinaryFile("cars.dat", ParkingLot1); ParkingLot2 = ReadBinaryFile("cars.dat", &cars_amount); // 此处可添加打印逻辑校验读取结果 // for(int i=0;i<cars_amount;i++){ // printf("%d %s\n", ParkingLot2[i].years, ParkingLot2[i].company); // } free(ParkingLot1); free(ParkingLot2); return 0; }
注意事项
- 只要在Windows Debug模式下看到值为
-842150451的int类型变量、或字符数组里出现连续0xCC对应的乱码,就可以直接判定对应内存未被正确写入有效数据,优先检查IO指针位置、内存拷贝逻辑是否正确。 - 二进制文件读写必须严格匹配打开模式:写入用
"wb"、读取用"rb",Windows平台下文本模式会自动转义换行符,会导致文件长度计算错误、内容读取异常。 - 如果一定要使用逐块读取计数的逻辑,计数循环结束后必须调用
rewind(f)将文件指针重置到起始位置,才能执行后续的批量读取操作,但这种写法IO效率低,考试场景下优先选择文件长度计算的方式,代码更短、出错概率更低。
内容的提问来源于stack exchange,提问作者Foch29
相关产品推荐
相关产品推荐

