不安全,仅当结构体满足 std::is_trivially_copyable_v 时才可 memcpy;含虚函数、非平凡成员、std::string、指针、对齐差异或跨平台时均会出错。

直接 memcpy 结构体是否安全?
多数情况下不安全。只有当结构体满足 std::is_trivially_copyable_v 时,memcpy 才能正确序列化。常见破坏条件包括:含虚函数、非平凡构造/析构函数、含 std::string / std::vector 等非 POD 成员、有字节对齐差异(如跨平台传输)、或含指针成员(只拷贝地址,不拷贝所指数据)。
验证方式:
static_assert(std::is_trivially_copyable_v, "MyStruct is not trivially copyable");
- 若断言失败,说明不能用
memcpy直接转二进制流 - 即使本地运行正常,跨编译器或跨平台(如 x86_64 ↔ ARM64)也可能因 padding、endianness 或 ABI 差异出错
- 调试时容易误判——“看起来能读出来” ≠ “语义正确”
含 std::string 或容器的结构体怎么序列化?
必须手动序列化每个字段,不能整体 memcpy。核心思路是:先写长度,再写内容;对嵌套对象递归处理。
例如:
立即学习“C++免费学习笔记(深入)”;
struct Person {
int id;
std::string name;
std::vector scores;
};
std::vector serialize(const Person& p) {
std::vector buf;
// 写 id(4 字节)
buf.insert(buf.end(), reinterpret_cast(&p.id),
reinterpret_cast(&p.id) + sizeof(p.id));
// 写 name 长度 + 数据
uint32_t len = static_cast(p.name.size());
buf.insert(buf.end(), reinterpret_cast(&len),
reinterpret_cast(&len) + sizeof(len));
buf.insert(buf.end(), p.name.begin(), p.name.end());
// 写 scores 大小和元素
len = static_cast(p.scores.size());
buf.insert(buf.end(), reinterpret_cast(&len),
reinterpret_cast(&len) + sizeof(len));
for (double d : p.scores) {
buf.insert(buf.end(), reinterpret_cast(&d),
reinterpret_cast(&d) + sizeof(d));
}
return buf;
}
- 注意
std::string::data()不保证以\0结尾,所以必须显式存长度 - 浮点数直接按位拷贝可跨平台(IEEE 754),但需统一 endianness(建议网络字节序,用
htons/htonl) - 未处理异常(如内存分配失败)或版本兼容性(后续加字段怎么办)
用 boost::serialization 还是 cereal?
cereal 更轻量、头文件即用、无运行时开销,适合嵌入式或性能敏感场景;boost::serialization 功能全但依赖大、编译慢、二进制格式不兼容旧版。
用 cereal 的典型写法:
#include#include #include struct Person { int id; std::string name; std::vector scores; template void serialize(Archive& ar) { ar(CEREAL_NVP(id), CEREAL_NVP(name), CEREAL_NVP(scores)); } }; // 序列化 std::ostringstream os; cereal::BinaryOutputArchive oarchive(os); oarchive(person); std::vector bin = os.str(); // 获取二进制流
- 必须为每个可序列化类型提供
serialize成员函数或非成员特化 -
cereal默认不处理多态(需额外宏),也不支持直接指针序列化(会报错) - 生成的二进制不是自描述的——反序列化端必须用完全相同的 struct 定义和 cereal 版本
跨平台传输时最容易忽略的三个点
哪怕结构体是 trivially copyable,跨平台仍可能失败:
-
int和long在不同平台宽度不同(Linux x86_64 中long是 8 字节,Windows 是 4 字节),应改用int32_t/uint64_t等固定宽度类型 - 大小端不一致:x86 是小端,ARM 可配,网络协议通常要求大端;建议所有整数字段在序列化前调用
htons/htonl/htobe64 - 结构体 padding 差异:同一定义在 GCC 和 MSVC 下可能因默认对齐策略不同而 layout 不同;用
#pragma pack(1)或[[gnu::packed]]强制紧凑,但要确认目标平台支持未对齐访问(否则 SIGBUS)
真正可靠的序列化,从来不是“把内存 dump 出来”,而是明确定义字段顺序、编码规则和边界行为。哪怕只是两个进程间通信,也值得花十分钟写个 serialize() / deserialize() 对。










