结构体能用 fwrite 直接写入文件仅当满足标准布局且不含指针、引用、虚函数或非平凡构造/析构函数;含 std::string 等动态成员会导致写入垃圾地址,须用 static_assert 检查并改用固定长度数组与紧凑对齐。

用 fwrite 写结构体前必须确认内存布局是否可直接序列化
结构体能用 fwrite 直接写入文件,仅当它满足“标准布局(standard layout)”且不含指针、引用、虚函数、非平凡构造/析构函数。否则写进去的是垃圾地址或未定义行为。
常见踩坑:含 std::string、std::vector、std::shared_ptr 的结构体——这些成员在内存中只存控制块指针,fwrite 会把指针值写进文件,读取时完全无法还原。
- 检查方式:
static_assert(std::is_standard_layout_v, "not standard layout"); - 禁用
std::string,改用固定长度 C 风格数组(如char name[32];) - 避免访问控制符混用(比如 public 成员后跟 private 成员),这可能破坏标准布局
fwrite 写结构体的正确调用姿势和参数含义
fwrite 本质是按字节拷贝内存块,不是“写结构体”,所以传参必须严格对应:地址、单个元素大小、元素个数、文件流。
错误写法:fwrite(&s, sizeof(s), 1, fp) 看似对,但若结构体有填充字节(padding),且你后续用不同编译器/平台读取,可能因填充位置不一致导致错位。
立即学习“C++免费学习笔记(深入)”;
- 务必用
sizeof(MyStruct)而非sizeof(s)(后者虽等价,但易掩盖类型变更风险) - 写多个时:
fwrite(arr, sizeof(MyStruct), count, fp),不要写成fwrite(arr, count * sizeof(MyStruct), 1, fp)—— 后者在出错时无法区分是写入0个还是1个大块 - 写完必须检查返回值:
if (fwrite(...) != 1) { /* handle error */ },fwrite返回成功写入的“项数”,不是字节数
跨平台/跨编译器读写结构体时必须处理字节序和对齐
即使结构体是 POD 类型,fwrite/fread 在 x86 和 ARM、MSVC 和 GCC 下仍可能因默认对齐策略不同而读错字段——尤其含 short/int/long 混排时。
例如:MSVC 默认 8 字节对齐,GCC 可能按成员自然对齐;一个含 char a; int b; 的结构体,在不同编译器下 sizeof 可能是 8 或 5(加 #pragma pack(1) 后)。
- 统一强制紧凑对齐:
#pragma pack(push, 1)包围结构体定义,再#pragma pack(pop) - 整数字段需手动转主机序→网络序(如用
htons/htonl),否则在小端机写、大端机读会全乱 - 文件头建议写 magic number + version + size,用于校验兼容性,避免静默读错
替代方案:什么时候不该用 fwrite 直接写结构体
只要结构体里有任何动态资源(哪怕只是 char* 指向堆内存)、或需要版本演进、或要被 Python/Java 等语言读取,fwrite 就不再是合理选择。
此时应切换为文本格式(JSON/INI)或二进制协议(Protocol Buffers、Cap’n Proto),哪怕多几行代码,也比后期调试字节错位强十倍。
-
fwrite适合:嵌入式日志缓存、同进程复用的临时快照、性能敏感且生命周期封闭的场景 - 不适合:持久化用户数据、网络传输、长期存档、含业务逻辑的配置
- 若坚持用二进制,至少封装一层:
MyStruct::serialize(FILE*)和MyStruct::deserialize(FILE*),把字节序/填充/校验逻辑收口
实际操作中最容易被忽略的,是填充字节与字节序的组合效应——同一段代码在本地测试全对,一上 ARM 服务器就解析失败,而且错误表现常是某个 int 字段值极小或极大,很难联想到是大小端问题。










