直接用指针序列化会崩溃,因为指针仅存储内存地址(如0x7fffa1b2c3d8),序列化后该地址在反序列化时已失效或被占用;std::string、std::vector等容器内部指针若被memcpy,只复制地址不复制数据,导致加载时段错误。

为什么直接用指针序列化会崩溃
因为指针本身只存地址,序列化时写进去的是一串随机内存地址(比如 0x7fffa1b2c3d8),反序列化时这个地址早已失效或被占用。更危险的是,std::string、std::vector 等容器内部用指针管理堆内存,直接 memcpy 它们的对象只会复制指针字段,不拷贝实际数据——结果是序列化出一堆无效地址,加载时必然段错误。
真正能序列化的只有“值”和“结构描述”
自定义序列化减体积的核心不是绕过标准方式,而是控制数据表达粒度:去掉类型名、字段名、冗余分隔符,用紧凑二进制布局替代 JSON/XML。指针在这里只起两个作用:一是作为临时中转(比如 reinterpret_cast<const uint8_t>(&x)</const> 读原始字节),二是指向待序列化的**有效数据块**(如 data() 返回的数组)。
- 基本类型(
int32_t、float)可直接用指针强转 +memcpy写入缓冲区,但必须保证目标平台字节序一致(跨平台需手动转换) -
std::string应先写长度(uint32_t),再写str.data()指向的字节流,不能写&str - 结构体要确保
#pragma pack(1)或alignas(1)消除填充字节,否则指针遍历到的偏移量会错位
如何避免手写序列化逻辑爆炸
纯靠指针操作每个字段极易出错,尤其字段增删后容易漏改偏移计算。推荐组合方案:
- 用宏或反射库(如
magic_enum+ 自定义 attribute)生成序列化函数骨架,指针仅用于最终写入阶段 - 对重复出现的小整数(如状态码、枚举),用变长整型(
varint)编码,uint32_t*指针可传给编码函数,比固定 4 字节省空间 - 字符串大量重复时,先构建字符串池,序列化时只写池索引(
uint16_t),用const char**指针快速查表
例如:一个含 3 个 std::string 的结构体,若都包含 "ERROR",池化后总开销是 5 字节(字符串)+ 3×2 字节(索引)= 11 字节;而直序列化是 3×(4+5)=27 字节。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
反序列化时指针怎么安全使用
关键原则:所有指针必须指向已分配且生命周期覆盖整个解析过程的内存。常见陷阱:
- 不要返回局部
std::vector<uint8_t></uint8_t>的.data(),它随函数返回销毁 - 用
std::unique_ptr<uint8_t></uint8_t>管理缓冲区,反序列化函数接收其get()返回的裸指针做解析 - 遇到长度前缀字段(如字符串长度),必须先校验后续缓冲区是否足够,否则
ptr + len会越界 —— 这是 crash 最高频原因
体积优化常伴随风险上升,比如关闭对齐、复用缓冲区、跳过边界检查,这些地方一旦指针算错偏移,调试成本远高于多占几字节。

















