Visual Studio下C++结构体多数据类型二进制一次性序列化与反序列化方案咨询
可行的二进制序列化/反序列化方案(无第三方依赖)
直接用fwrite(&frame, sizeof(Frame), 1, file)这种方式读写整个结构体是行不通的——问题就出在std::string上:它内部存储的是指向堆内存的指针,而不是字符串的实际内容。写入文件的只是这个指针的地址,反序列化时读回来的地址在当前进程中是无效的,自然会触发内存错误。
不过我们可以自己实现一套轻量的序列化逻辑,把结构体的所有数据(包括字符串的实际内容)按固定格式写入文件,反序列化时再按格式重建结构体。虽然不是字面意义上的“一次性读写整个结构体”,但可以封装成两个函数,调用起来和一次性操作一样方便,同时完全不依赖第三方库。
具体实现步骤
核心思路是:先序列化所有POD类型(bool、uint64_t、uint32_t、float这些都是POD),再单独处理std::string——先写字符串的长度,再写字符串的字节内容。反序列化时按完全相反的顺序读取。
下面是针对你的Frame结构体的完整代码示例:
#include <fstream> #include <string> #include <cstdint> struct Frame { bool isPass{ true }; uint64_t address{ 0 }; uint32_t age{ 0 }; float marks{ 0.0 }; std::string userName; }; // 序列化Frame到二进制文件 bool serializeFrame(const Frame& frame, const std::string& filePath) { std::ofstream file(filePath, std::ios::binary); if (!file.is_open()) return false; // 写入POD类型数据 file.write(reinterpret_cast<const char*>(&frame.isPass), sizeof(frame.isPass)); file.write(reinterpret_cast<const char*>(&frame.address), sizeof(frame.address)); file.write(reinterpret_cast<const char*>(&frame.age), sizeof(frame.age)); file.write(reinterpret_cast<const char*>(&frame.marks), sizeof(frame.marks)); // 处理std::string:先写长度(用uint32_t保证跨平台兼容性) uint32_t strLen = static_cast<uint32_t>(frame.userName.size()); file.write(reinterpret_cast<const char*>(&strLen), sizeof(strLen)); // 再写字符串内容 if (strLen > 0) { file.write(frame.userName.data(), strLen); } return file.good(); } // 从二进制文件反序列化Frame bool deserializeFrame(Frame& frame, const std::string& filePath) { std::ifstream file(filePath, std::ios::binary); if (!file.is_open()) return false; // 读取POD类型数据 file.read(reinterpret_cast<char*>(&frame.isPass), sizeof(frame.isPass)); file.read(reinterpret_cast<char*>(&frame.address), sizeof(frame.address)); file.read(reinterpret_cast<char*>(&frame.age), sizeof(frame.age)); file.read(reinterpret_cast<char*>(&frame.marks), sizeof(frame.marks)); // 处理std::string:先读长度 uint32_t strLen = 0; file.read(reinterpret_cast<char*>(&strLen), sizeof(strLen)); // 分配空间并读取内容 frame.userName.resize(strLen); if (strLen > 0) { file.read(&frame.userName[0], strLen); } return file.good(); } // 测试示例 int main() { Frame originalFrame; originalFrame.isPass = false; originalFrame.address = 0x123456789ABCDEF0; originalFrame.age = 25; originalFrame.marks = 92.5f; originalFrame.userName = "JohnDoe"; // 序列化 if (serializeFrame(originalFrame, "frame.bin")) { Frame restoredFrame; // 反序列化 if (deserializeFrame(restoredFrame, "frame.bin")) { // 验证数据是否一致 bool isValid = (originalFrame.isPass == restoredFrame.isPass) && (originalFrame.address == restoredFrame.address) && (originalFrame.age == restoredFrame.age) && (originalFrame.marks == restoredFrame.marks) && (originalFrame.userName == restoredFrame.userName); // isValid应该为true } } return 0; }
关键细节说明
- POD类型直接读写:像
bool、uint64_t这些POD类型可以直接用write/read操作,因为它们的内存布局是连续且固定的。 - std::string的处理:必须先写入字符串的长度,这样反序列化时才能知道要分配多少内存来存储字符串内容,避免内存溢出或截断。
- 字节序(可选):如果需要跨不同架构的平台(比如x86和ARM)读写文件,需要把整数类型(比如
uint64_t、uint32_t)转换成网络字节序再写入,读取时再转换回来。可以用htonll/ntohll(处理64位)、htonl/ntohl(处理32位)这类函数。如果只在同一平台使用,这一步可以省略。 - 封装性:把序列化和反序列化逻辑封装成函数后,调用方只需要传入结构体和文件路径,和“一次性读写”的体验几乎一致,同时避免了逐个字段处理的繁琐。
这种方案完全不需要依赖任何开源库,只用到C++标准库的功能,同时完美解决std::string的内存分配问题。
内容的提问来源于stack exchange,提问作者Rav
相关产品推荐
相关产品推荐

