直接fwrite结构体含指针成员会写入地址而非数据,属浅序列化;需深度序列化:先写长度再写内容,嵌套指针结构须逐层处理。

结构体含 char* 或 int* 时,直接 fwrite 会写入地址而非数据
这是最常踩的坑:结构体里存的是指针,fwrite(&s, sizeof(s), 1, fp) 只把指针值(比如 0x7fffabcd1234)写进文件,读取时无法还原原始内容。本质是「浅序列化」,没处理指针指向的堆内存。
- 适用场景:需要跨进程/重启持久化结构体,且字段含动态分配内存(如字符串、数组)
- 必须手动递归处理每个指针成员——即「深度优先序列化」:先写长度,再写内容,最后才是结构体其他字段(或按需调整顺序)
- 若结构体嵌套另一含指针的结构体,同样要逐层展开,不能依赖
sizeof
std::ofstream 配合 write() 写指针内容时,必须显式传长度
C++ 流不自动识别 char* 是字符串还是二进制块,os 会尝试输出地址地址(甚至触发未定义行为),必须用 <code>write() 并明确告诉它写多少字节。
- 错误写法:
os → 写出地址,不是字符串 - 正确写法:
os.write(s.name, s.name_len);,前提是结构体里存了长度字段(如size_t name_len;) - 若用
std::string替代char*,仍需先写str.size(),再写str.data(),否则读取端无法知道字符串多长 - 注意字节序:跨平台时,整数长度字段建议用
htole32()等统一转小端,避免读取错乱
反序列化时,malloc / new 分配内存前必须校验读取的长度是否合理
攻击者或损坏文件可能让长度字段极大(如 0xFFFFFFFF),直接 new char[len] 会导致 OOM 或崩溃。
- 读取长度后,先判断是否超出预期上限(例如单个字符串不超过 1MB):
if (len > 1024 * 1024) { /* 拒绝解析 */ } - 用
std::vector<char></char>替代裸指针更安全,构造时会检查容量;但注意vector.data()返回的仍是裸指针,后续仍需自己管理生命周期 - 若结构体有多个指针字段,按写入顺序严格读取,顺序错一位,后面全乱——建议在文件头加 magic number 和版本号,便于快速识别格式是否匹配
用 std::span 或自定义序列化函数封装重复逻辑,避免手写多遍 write/read
每次增减一个指针字段,都要改写入/读取逻辑,极易遗漏或顺序不一致。靠人工维护不可靠。
- 推荐为结构体写两个配套函数:
serialize(std::ostream&)和deserialize(std::istream&),把长度+内容写入逻辑收拢 - 若用 C++20,
std::span<const std::byte></const>可统一处理任意类型二进制块,减少reinterpret_cast的使用频次 - 不要试图用宏或模板全自动推导指针成员——编译器看不到运行时分配关系,这种「自动化」反而掩盖了深度序列化的意图,出错时更难定位
真正麻烦的从来不是怎么写进去,而是读出来时如何确保每一块内存都按原样重建、长度不越界、顺序不颠倒。这些校验和约定,比序列化本身更消耗心力。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











