直接用 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) 为 size、1 为 count 调用,返回 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):
立即学习“C++免费学习笔记(深入)”;
#include <cstdio>
#include <iostream><p>struct Record {
int id;
float score;
char tag[8];
};</p><p>int main() {
// 写入
FILE* fp = fopen("records.dat", "wb");
if (!fp) { std::cerr << "无法写入文件\n"; return 1; }</p><pre class='brush:php;toolbar:false;'>Record arr[] = {{101, 95.5f, "A"}, {202, 87.0f, "B"}};
for (int i = 0; i < 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 << "无法读取文件\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 << "id=" << r.id << ", score=" << r.score << ", tag=" << r.tag << "\n";
}
if (ferror(fp)) std::cerr << "读取发生 I/O 错误\n";
fclose(fp);
return 0;}
关键点在于:每个字段单独 fwrite/fread,完全绕开结构体内存布局;每步都检查返回值;关闭文件前确保无未刷新缓冲区(fclose 自动 flush)。
真正麻烦的从来不是调用函数本身,而是对齐、错误码、模式字符串这三个地方漏掉任意一个,整个二进制读写就会静默失败。









