std::string 不该直接 memcpy 到二进制流,因其内部含指针,memcpy 仅复制地址导致反序列化时野指针;正确做法是先写长度再写内容字节,并统一处理字节序与对齐。

为什么 std::string 不该直接 memcpy 到二进制流里
因为序列化不是“把内存 dump 出去”,而是把逻辑结构稳定地映射成字节序列。用 memcpy(&obj, buf, sizeof(obj)) 看似快,但会踩中对齐、填充字节、指针悬空、非 POD 类型(如 std::string)等坑——它只存了指针地址,反序列化时读出来就是野指针。
真正安全的做法是显式控制每个字段的序列化行为:
-
std::string必须先写长度(uint32_t),再写内容字节,否则无法知道读多少 - 浮点数跨平台需统一用
memcpy转成整型再写(避免 x87 扩展精度残留) - 结构体字段顺序必须和反序列化端严格一致,建议加
static_assert(std::is_standard_layout_v<t>)</t>
如何让 flatbuffers 在不牺牲性能的前提下支持运行时 schema 变更
flatbuffers 原生不支持动态字段增删,但实际业务中常需要兼容旧版本客户端。硬编码 schema 会导致每次字段变更都要发新包,而全量 JSON 又太慢。
折中方案是:用 flatbuffers 的 flexbuffer 子模块承载可选字段,主结构仍走高效 flatbuffer;关键字段(如 ID、type)保留在 flatbuffer 根表,扩展字段走 flexbuffer::Map:
立即学习“C++免费学习笔记(深入)”;
- 主结构定义保持
table,字段全required,保证解析零拷贝 - 新增字段不进主表,统一塞进一个
optional_data: [ubyte]字段,反序列化时用flexbuffer::GetRoot解析 - 服务端写入前做
flexbuffer::Builder预校验,避免无效 JSON 导致整个 buffer 失效
protobuf 的 parseFromArray 为什么比 parseFromIstream 快 3–5 倍
根本区别不在解析逻辑,而在内存访问模式:parseFromArray 接收连续内存块,可利用 CPU 预取和 SIMD 加速跳过未知字段;parseFromIstream 每次读都可能触发系统调用、缓存未命中,还强制按字节流处理,无法批量跳过。
但要注意前提条件:
- 必须确保传入的
const void*指向完整、已加载的 buffer,不能是 mmap 映射的缺页区域 - buffer 生命周期必须长于解析过程,别传栈变量地址或临时
std::vector::data()(析构后失效) - 若数据来自网络,务必先收全再调用,不要边收边 parse——protobuf 不支持流式 partial parse
自定义序列化函数里,std::endian 和 htonl 怎么选
跨网络传输或跨架构存储时,字节序必须显式约定。C++23 引入 std::endian,但仅用于编译期检测;真正转换还得靠 htonl/htons 或手动位移。
实操建议:
- 网络传输一律用
htonl(32 位)、htons(16 位),它们在所有 POSIX 平台语义明确且内联优化充分 - 文件存储若需长期兼容,也推荐用网络序,避免未来 ARM/Mac 迁移出问题
- 别依赖
std::byteswap,它不区分用途,且 MSVC 对__builtin_bswap支持不稳定 - 小端机器上写 float/double,先
memcpy到uint32_t/uint64_t,再htonl,不能直接对浮点变量取地址转序
最易被忽略的一点:结构体嵌套时,每个子对象的字节序转换必须独立进行,没有“全局字节序开关”这种东西——漏掉一个字段,整条链路就错位。











