memcpy直接序列化含裸指针的结构体必然失败,因其仅复制无效地址而非实际数据;正确做法是手动深拷贝指针所指内容并配套长度字段,或改用std::string/std::vector等RAII容器。

结构体含裸指针时,memcpy 直接序列化必然失败
二进制序列化裸指针本身毫无意义——它只存一个地址值,反序列化到另一块内存后,该地址大概率已失效或指向非法区域。常见现象是程序崩溃、读到垃圾值,或看似正常但后续访问 segfault。这不是“序列化不完整”,而是根本没做深拷贝逻辑。
真正要做的,是把指针所指向的**实际数据内容**(比如字符串、数组、嵌套对象)一并写入字节流,并在反序列化时重新 malloc 或 new 内存,再把内容复制进去。
实操建议:
- 结构体中每个指针成员必须配套记录其长度(如
char*要存size_t len字段) - 序列化顺序必须与反序列化严格一致:先写长度字段,再写数据内容
- 避免直接对结构体整体
sizeof(Struct)读写——它包含指针值、对齐填充,且不同编译器/平台布局可能不同 - 若用
std::vector当缓冲区,注意data()返回的指针生命周期,别在 vector realloc 后继续用旧指针
手动序列化需自己管理内存分配和边界,std::string 和 std::vector 是更安全的替代方案
裸指针带来的所有麻烦,本质是所有权和生命周期失控。用 std::string 替代 char*,用 std::vector 替代 int*,能直接避开深拷贝的手动实现。
示例对比:
struct Bad {
char* name;
int* data;
size_t name_len;
size_t data_size;
};
<p>struct Good {
std::string name; // 自带 length() + data()
std::vector<int> data; // 自带 size() + data()
};</int></p>
对 Good,只要重载 operator 或写个简单 serialize() 成员函数,遍历 name.data() 和 data.data() 即可;反序列化时直接 resize() 后 memcpy,无需 new 或异常处理。
关键差异:
-
std::string在 C++11 后基本保证连续存储(&s[0]可用),且size()是可靠长度 -
std::vector::data()永远非空(即使 size=0),比裸指针判空更稳 - 移动语义让深拷贝成本可控,且异常安全——裸指针
new失败时容易泄漏
如果必须用裸指针(如对接 C 接口或性能敏感场景),序列化函数必须显式处理每级指针
没有通用“自动深拷贝”机制。每个指针类型、每层嵌套都要单独编码逻辑。例如结构体里有个 Node* 指向链表,就得递归遍历整个链表,逐节点写入长度+内容+next 偏移量(不能写原始地址)。
典型错误:
- 只序列化了指针值(4 或 8 字节),没序列化它指向的数据
- 反序列化时用
reinterpret_cast强转字节流为结构体指针——这在有虚函数、非 POD 类型上必崩 - 忽略字节序(network byte order):跨平台传输时,
uint32_t len必须用htonl()存,ntohl()读 - 未对齐读写:比如从 char buffer 里直接
*(int64_t*)ptr,在 ARM 或某些 x86 配置下触发bus error
安全做法:
- 用
memcpy+ 显式类型转换,例如memcpy(&len, buf + offset, sizeof(len)); offset += sizeof(len); - 所有整数字段统一用固定宽类型(
uint32_t,int64_t),避免int平台差异 - 结构体定义加
static_assert(std::is_trivially_copyable_v防止误用于非 POD 类型, "...");
反序列化时 new 出来的内存必须由使用者明确释放,RAII 容器能大幅降低出错概率
手动 new 出来的指针,反序列化函数返回后,调用方就承担了 delete 责任。但谁来 delete?什么时候 delete?多个拷贝共享同一块内存?这些问题在裸指针方案里全靠人工约定,极易出错。
更现实的做法:
- 反序列化函数返回
std::unique_ptr,所有权清晰,析构自动清理 - 若需共享,用
std::shared_ptr,但注意循环引用风险(尤其含指针成员的结构体) - 完全避免裸指针暴露:结构体内用
std::unique_ptr包一层,序列化时解引用取.get(),反序列化时std::make_unique - 千万别在结构体里混用裸指针和智能指针——语义混乱,析构行为不可预测
最常被忽略的一点:序列化格式一旦发布(比如存成文件或发给其他进程),就变成了 ABI。哪怕只是改了个字段顺序,旧数据就再也读不回来了。所以字段增减、类型变更、甚至注释位置变动,都得考虑兼容性——裸指针方案在这点上尤其脆弱,因为没元信息校验机制。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











