直接用 fwrite 写结构体可行,但必须满足结构体是平凡类型(trivial)且标准布局(standard-layout),否则会写入无效指针或跳过虚表;需用 static_assert 编译期检查,并手动序列化 std::string 等非 POD 成员。

直接用 fwrite 写结构体可行,但必须满足「无虚函数、无指针、无非POD成员」
只要结构体是标准布局(standard-layout)且是平凡类型(trivial),fwrite 就能安全写入二进制。否则会写入野指针地址、跳过虚表、或把 string 对象的内部指针而非实际字符串内容写进去。
常见踩坑点:
-
std::string、std::vector、std::shared_ptr等不能直接 fwrite —— 它们不是 POD 类型 - 含虚函数的 struct/class 有隐藏的 vptr,fwrite 会把它当成普通字节写入,读取时无法还原对象语义
- 编译器可能加 padding,不同平台/编译选项下
sizeof(MyStruct)可能不同,影响跨平台读取
怎么判断结构体能不能直接 fwrite
最简单办法:在代码里加静态断言,编译期检查
static_assert(std::is_trivial_v, "MyStruct must be trivial"); static_assert(std::is_standard_layout_v , "MyStruct must be standard layout");
如果编译不报错,说明它可位拷贝(bitwise copyable),fwrite 是安全的。也可以用 std::is_pod_v(C++17 起已弃用,但语义等价)。
立即学习“C++免费学习笔记(深入)”;
典型安全结构体长这样:
struct Vec3 {
float x, y, z;
}; // ✅ 全是基本类型,无构造函数,无虚函数而这样就不行:
struct Bad {
std::string name; // ❌ 非 POD,内部是指针
int* data; // ❌ 指针字段
virtual void f() {} // ❌ 有虚函数
};写文件时 fwrite 的正确调用方式
核心是:确保传入的指针有效、size 和 count 匹配、文件以 "wb" 模式打开
- 不要写
fwrite(&s, sizeof(s), 1, fp)后还指望读出来能直接用 —— 除非你确认结构体可位拷贝且读取端完全一致 - 写多个结构体时,推荐用
fwrite(arr, sizeof(MyStruct), n, fp),比循环调用更高效 - 务必检查返回值:
if (fwrite(...)!=1) { /* error */ },尤其在嵌入式或磁盘满时容易静默失败 - Windows 下注意文本模式会把
\n转成\r\n,必须用"wb",Linux 下虽不转换,也建议统一用"wb"避免歧义
结构体含 std::string 怎么办?得手动序列化
不能绕过,必须拆开处理:先写字符串长度(size_t),再写字符数据(c_str())
struct Person {
int id;
std::string name;
};
void write_person(FILE* fp, const Person& p) {
fwrite(&p.id, sizeof(p.id), 1, fp);
size_t len = p.name.size();
fwrite(&len, sizeof(len), 1, fp);
if (len > 0) {
fwrite(p.name.c_str(), 1, len, fp);
}
}
读的时候也要严格按这个顺序反向操作,且注意 size_t 在 32/64 位系统上大小不同 —— 如果要跨平台,改用固定宽度类型如 uint32_t。
更复杂的情况(嵌套结构、变长数组、对齐要求高)建议用 memcpy + 自定义 buffer,或者换用 std::ofstream.write 配合 reinterpret_cast,逻辑一致,只是 C++ 风格稍强。
真正麻烦的从来不是怎么写,而是怎么保证读出来的每个字节都和写进去时语义一致 —— 尤其当结构体在不同编译器、不同 ABI、甚至不同语言(比如 Python 用 struct.unpack 读)之间传递时,对齐、字节序、padding 都得显式约定清楚。










