C++自定义对象反序列化后析构触发访问违规错误的排查与解决
C++自定义序列化器反序列化后访问违规问题排查与解决
问题描述
编写C自定义对象序列化器时,序列化过程无异常,但反序列化后,在程序关闭或对象清理阶段触发访问违规错误。运行平台为Win10,所有C版本均出现相同问题,改用指针版本也无法解决。
错误信息
错误1:
Exception thrown at 0x00007FF790BFADED in app.exe: 0xC0000005: Access violation reading location 0x000001E4E61DBED0
错误2:
Exception thrown: read access violation. _Pnext was 0xFFFFFFFFFFFFFFFF
相关代码
序列化器头文件(ObjectSerialization.h)
#ifndef OBJ_SER_API_H #define OBJ_SER_API_H #include <fstream> class ObjectSerialization { public: /// <summary> /// Serialize the provided object to a binary file. /// </summary> /// <param name="filePath">The path to the binary file where the object will be serialized.</param> /// <param name="objectToWrite">The object to be serialized.</param> template<typename T> static void Serialize(const wchar_t* filePath, const T& objectToWrite) { std::ofstream file(filePath, std::ios::binary); file.write(reinterpret_cast<const char*>(&objectToWrite), sizeof(objectToWrite)); file.close(); } /// <summary> /// Deserialize an object from a binary file. /// </summary> /// <param name="filePath">The path to the binary file from which the object will be deserialized.</param> /// <returns>The deserialized object.</returns> template<typename T> static T Deserialize(const wchar_t* filePath) { T object; std::ifstream file(filePath, std::ios::binary); file.read(reinterpret_cast<char*>(&object), sizeof(object)); file.close(); return object; } }; #endif // !OBJ_SER_API_H
主函数代码
#include <iostream> #include <string> #include "ObjectSerialization.h" class Student { public: std::wstring Name; int Age = 0; }; int main() { // Serialization { Student student; student.Name = L"Michael"; student.Age = 22; ObjectSerialization::Serialize<Student>(L"Student.dat", student); } // Deserialization { Student student = ObjectSerialization::Deserialize<Student>(L"Student.dat"); std::wcout << student.Name << std::endl; std::cout << student.Age << std::endl; } // <- exception occurred std::cin.get(); return 0; }
问题根源
你的序列化方式是直接对对象进行内存二进制拷贝,这种方式仅适用于平凡类型(Trivial Type)——即没有自定义构造/析构函数、没有非平凡成员变量、内部不持有堆内存指针的类型。
但Student类包含std::wstring,这是一个非平凡类型:它内部维护着指向堆内存的指针,以及字符串长度、容量等状态。序列化时,你只是把这些指针值、长度、容量等内存数据写入了文件;反序列化时,这些指针指向的内存地址在当前进程中是无效的(原进程的堆内存已释放,或当前进程的该地址不属于你的程序)。当Student对象销毁时,std::wstring的析构函数尝试释放这个无效指针指向的内存,就触发了访问违规错误。
解决方案
方案1:为自定义类型实现成员级序列化/反序列化
修改序列化器,针对非平凡类型(如std::wstring)单独处理,逐个成员完成序列化与反序列化:
修改后的序列化器头文件
#ifndef OBJ_SER_API_H #define OBJ_SER_API_H #include <fstream> #include <string> // 提前声明Student类,用于模板特化 class Student; class ObjectSerialization { public: // 序列化Student类型(模板特化) template<> static void Serialize<Student>(const wchar_t* filePath, const Student& student) { std::ofstream file(filePath, std::ios::binary); // 先写入Age file.write(reinterpret_cast<const char*>(&student.Age), sizeof(student.Age)); // 写入Name的长度和数据 size_t nameLen = student.Name.size(); file.write(reinterpret_cast<const char*>(&nameLen), sizeof(nameLen)); file.write(reinterpret_cast<const char*>(student.Name.data()), nameLen * sizeof(wchar_t)); file.close(); } // 反序列化Student类型(模板特化) template<> static Student Deserialize<Student>(const wchar_t* filePath) { Student student; std::ifstream file(filePath, std::ios::binary); // 读取Age file.read(reinterpret_cast<char*>(&student.Age), sizeof(student.Age)); // 读取Name的长度和数据 size_t nameLen = 0; file.read(reinterpret_cast<char*>(&nameLen), sizeof(nameLen)); student.Name.resize(nameLen); file.read(reinterpret_cast<char*>(student.Name.data()), nameLen * sizeof(wchar_t)); file.close(); return student; } }; #endif // !OBJ_SER_API_H
修改后的主函数
#include <iostream> #include <string> #include "ObjectSerialization.h" class Student { public: std::wstring Name; int Age = 0; }; // 显式实例化模板特化,避免链接错误 template void ObjectSerialization::Serialize<Student>(const wchar_t*, const Student&); template Student ObjectSerialization::Deserialize<Student>(const wchar_t*); int main() { // Serialization { Student student; student.Name = L"Michael"; student.Age = 22; ObjectSerialization::Serialize<Student>(L"Student.dat", student); } // Deserialization { Student student = ObjectSerialization::Deserialize<Student>(L"Student.dat"); std::wcout << student.Name << std::endl; std::cout << student.Age << std::endl; } std::cin.get(); return 0; }
方案2:使用通用序列化框架
如果需要序列化大量复杂类型,可以使用成熟的C++序列化库,比如Boost.Serialization、Protobuf等,这些库会自动处理非平凡类型的序列化逻辑,避免手动实现的繁琐。
内容的提问来源于stack exchange,提问作者Koray Üstündağ
相关产品推荐
相关产品推荐

