存档结构必须带版本号字段,否则旧存档遇结构微调将解析失败或内存错乱;建议在开头固定写入4字节version,按破坏性变更递增,并显式逐字段序列化、新增字段设默认值且可降级忽略,同时覆盖跨版本读取测试。

存档结构必须带版本号字段
不加版本号的存档,等于没留退路。读取时一旦结构微调(比如新增一个 health 成员),旧存档直接解析失败或内存错乱,玩家进度就丢了。
最简方案:在每个存档二进制/文本块开头固定写入 4 字节整数 version,比如 1、2。序列化函数入口先读这个值,再分支处理。
- 别用宏定义版本常量——改了宏但忘了更新已存档文件,照样崩
- 版本号建议从
1开始,每次破坏性变更(字段删/改类型/重命名)才递增,兼容性变更(如新增可选字段)可不升版 - 如果用 JSON 或 YAML,把
"version": 2放在根对象第一项,解析时优先检查它
避免直接序列化 class 内存布局
memcpy(&buffer, &obj, sizeof(obj)) 看起来快,实则埋雷:编译器可能加 padding、虚函数表指针位置不固定、成员顺序受 #pragma pack 影响——换编译器或升级 STL 就读不出来。
真正可控的方式是显式逐字段序列化,哪怕多写几行。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 对
std::vector<int>,先写长度size(),再循环写每个data[i];别直接写data()指针 - 对
std::string,同样先写长度,再写字符数组;别依赖内部c_str()布局 - 枚举类型统一转成
int存,防止不同编译器底层用char还是short不一致
新增字段必须默认可降级忽略
版本 2 存档多了个 last_checkpoint_x,但版本 1 的加载逻辑不能因此崩溃。核心原则:新字段只读不写,旧版本读到不认识的字段就跳过。
具体怎么做取决于格式:
- 二进制格式:设计字段头(tag-length-value),遇到未知
tag就按length跳过字节 - JSON 格式:用
json.find("last_checkpoint_x") != json.end()判断是否存在,不存在就用默认值(如0.0f) - 关键点:所有新增字段必须有合理默认值,且旧逻辑能安全绕过它继续运行
测试必须覆盖跨版本读取路径
光测“本版本存→本版本读”没用。真问题永远出在“v1 存→v2 读”或“v2 存→v1 读(报错提示要友好)”。
- 手动保留一份 v1 存档文件,每次发版前用新代码加载它,确认不 crash、关键数据(角色血量、位置)正确
- 写个简单脚本,自动生成 v1/v2/v3 的测试存档,跑自动化校验:字段数量、数值范围、结构完整性
- 特别注意浮点数精度——
float和double混用会导致看似相同的值在跨版本时被误判为变更
版本兼容不是加个字段号就完事,是每次改存档结构时都得问一句:老玩家打开游戏,还能站在他上次退出的地方吗?


















