17370845950

C++使用fwrite和fread操作二进制文件完整示例
直接用 fwrite 写结构体易因字节对齐导致读取错位,应逐字段写入并检查 fread 返回值,二进制文件必须用 "wb"/"rb" 模式打开。

fwrite 写入结构体时字节对齐会导致读取错位

直接用 fwrite 写结构体看似简单,但结构体成员默认按编译器对齐规则填充空白字节。如果写入端和读取端的结构体定义或编译选项(如 #pragma pack)不一致,fread 会把填充字节误读为有效数据,导致字段值全乱。

  • 写入前确认结构体实际大小:用 sizeof(MyStruct) 打印,别只看成员总和
  • 强制取消对齐(推荐):
    #pragma pack(1)
    struct Data {
        int id;
        double value;
        char name[32];
    };
    #pragma pack()
  • 更安全的做法是逐字段写入,避开结

    构体内存布局问题

fread 返回值不等于请求字节数就说明出错了

fread 的返回值是「成功读取的元素个数」,不是字节数。若以 sizeof(struct)size1count 调用,返回 1 才算完整读取;返回 0 不一定代表文件结束,可能是读取失败(比如磁盘权限不足或文件被截断)。

  • 必须检查 ferror(fp)feof(fp) 区分错误类型
  • 不要用 while (!feof(fp)) 循环读取——这是经典陷阱,会导致最后一次读取重复处理
  • 正确模式:
    while (fread(&data, sizeof(data), 1, fp) == 1) {
        // 处理 data
    }

二进制文件必须用 "wb""rb" 模式打开

在 Windows 上,文本模式("w"/"r")会把 \n 自动转成 \r\n,破坏原始字节流;Linux 虽然影响小,但跨平台代码必须统一用二进制模式。

  • fopen("data.bin", "wb") 写入前清空文件并禁用换行转换
  • fopen("data.bin", "rb") 读取时不解释任何字节为控制字符
  • "ab" 追加写入时也要配 "rb" 读取,否则可能读到旧数据末尾的垃圾字节

完整可运行示例:写入/读取结构体数组

下面是一个不依赖对齐、显式控制字节流的最小可行版本,适用于 C++ 编译器(g++ / clang++ / MSVC):

#include 
#include 

struct Record { int id; float score; char tag[8]; };

int main() { // 写入 FILE* fp = fopen("records.dat", "wb"); if (!fp) { std::cerr << "无法写入文件\n"; return 1; }

Record arr[] = {{101, 95.5f, "A"}, {202, 87.0f, "B"}};
for (int i = 0; i zuojiankuohaophpcn 2; ++i) {
    fwrite(&arr[i].id, sizeof(int), 1, fp);
    fwrite(&arr[i].score, sizeof(float), 1, fp);
    fwrite(arr[i].tag, sizeof(arr[i].tag), 1, fp);
}
fclose(fp);

// 读取
fp = fopen("records.dat", "rb");
if (!fp) { std::cerr zuojiankuohaophpcnzuojiankuohaophpcn "无法读取文件\n"; return 1; }

Record r;
while (fread(&r.id, sizeof(int), 1, fp) == 1 &&
       fread(&r.score, sizeof(float), 1, fp) == 1 &&
       fread(r.tag, sizeof(r.tag), 1, fp) == 1) {
    std::cout zuojiankuohaophpcnzuojiankuohaophpcn "id=" zuojiankuohaophpcnzuojiankuohaophpcn r.id zuojiankuohaophpcnzuojiankuohaophpcn ", score=" zuojiankuohaophpcnzuojiankuohaophpcn r.score zuojiankuohaophpcnzuojiankuohaophpcn ", tag=" zuojiankuohaophpcnzuojiankuohaophpcn r.tag zuojiankuohaophpcnzuojiankuohaophpcn "\n";
}

if (ferror(fp)) std::cerr zuojiankuohaophpcnzuojiankuohaophpcn "读取发生 I/O 错误\n";
fclose(fp);
return 0;

}

关键点在于:每个字段单独 fwrite/fread,完全绕开结构体内存布局;每步都检查返回值;关闭文件前确保无未刷新缓冲区(fclose 自动 flush)。

真正麻烦的从来不是调用函数本身,而是对齐、错误码、模式字符串这三个地方漏掉任意一个,整个二进制读写就会静默失败。