直接用 std::ofstream 写 raw memory 会崩溃,因为虚表指针、指针成员、std::string 或 std::vector 的内部堆地址不可跨进程/时间复用,直接 write() 含指针 struct 会导致读取时解引用段错误。

直接用 std::ofstream 写 raw memory 会崩溃,为什么?
因为 C++ 对象内存布局不等于可序列化镜像:虚表指针、指针成员、std::string 或 std::vector 内部堆地址全不可跨进程/跨时间复用。直接 write() 一个含指针的 struct,读取时解引用必然段错误。
常见错误现象:Segmentation fault、读出乱码、std::bad_alloc(反序列化时 new 失败)、对象字段值异常(如 size=18446744073709551615)。
实操建议:
- 必须区分「数据」和「引用」——所有指针/智能指针/容器都需显式序列化其指向内容,而非地址本身
- 禁止对含虚函数的类做
memcpy-style 序列化;若要用,得先static_assert(!std::is_polymorphic_v<t>)</t> - 优先使用「自描述结构」:在二进制开头写版本号、总长度、对象数量等元信息,便于后续兼容与调试
用 boost::serialization 处理跨对象指针引用
它能自动识别重复指针(比如 A→B、C→B),只序列化 B 一次,并在反序列化时重建相同地址关系。但默认不启用,需显式标记。
使用场景:有 std::shared_ptr<node></node>、Node*、或循环引用(A→B→A)的图状结构。
实操建议:
- 每个参与序列化的类必须定义
serialize(Archive&, unsigned)成员函数,并用ar & member;逐字段注册 - 对裸指针,必须用
ar & BOOST_SERIALIZATION_NVP(ptr);且确保生命周期由序列化框架管理(推荐改用shared_ptr) - 开启跟踪支持:在类定义后加
BOOST_CLASS_TRACKING(T, boost::serialization::track_always),否则重复指针会被多次序列化 - 二进制归档需用
boost::archive::binary_oarchive+std::ofstream(..., std::ios::binary),缺std::ios::binary会导致 Windows 下换行符被篡改
手写序列化时如何处理 std::vector<:shared_ptr>></:shared_ptr> 的嵌套层级
不能只写 vector.size() 然后 for 循环 write(reinterpret_cast<char>(&ptr), sizeof(ptr))</char>——那只是序列化了智能指针控制块地址,毫无意义。
正确做法是把「对象树」转为「对象平面列表 + 引用映射表」:
- 第一遍遍历:收集所有可达对象(用
std::set<const void></const>去重),分配唯一 ID(如 0,1,2…),存入std::map<const void uint32_t> id_map</const> - 第二遍序列化:先写 ID 总数,再按 ID 顺序写每个对象的原始数据(不含指针字段),对每个指针字段只写其目标 ID(uint32_t)
- 反序列化时:先读所有对象数据到临时缓冲区,构建 ID→新对象指针映射;再扫一遍,用 ID 查表填充指针字段
- 注意
std::shared_ptr的控制块无法手动重建,必须用std::make_shared创建后,再通过std::static_pointer_cast转型
性能关键点:避免 std::string 频繁小内存分配
序列化 10 万个含 std::string 的对象时,如果每字符串单独 write(len); write(data),会产生大量小 IO 和内存碎片。
实操建议:
- 预估总字符串长度,一次性 malloc 一块大 buffer,用偏移量写入(类似 arena allocator)
- 字符串字段统一改为「长度前缀 + 数据」格式,长度用
uint32_t(别用size_t,跨 32/64 位平台不兼容) - 禁用
std::string的 SSO(small string optimization)干扰:序列化前调用s.reserve(s.size())强制走堆内存,保证s.data()总有效 - 若允许牺牲一点空间换速度,可用
std::string_view仅作序列化中间视图,但反序列化仍需 new 出真实std::string
跨对象引用结构的序列化难点不在“怎么写”,而在“怎么定义对象边界”和“谁负责释放”。哪怕用了 boost::serialization,一旦混用裸指针和智能指针,或忘记 track_always,镜像就不可靠。二进制持久化不是快照,是契约——结构变、版本号必须升,否则读出来的是静默错误。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











