问题:
我使用std::fstream
以二进制模式将对象写入文件。但是,当我从该文件读回另一个对象,然后调用此新对象的虚拟成员函数之一时,存在内存访问冲突错误。
这是代码的样子:
class A
{
public:
// data
virtual void foo() = 0;
};
class B: public A
{
public:
// added data
virtual void foo() { ... }
}
int main()
{
// ...
A* a = new B();
A* b = new B();
file.write((char*)a, sizeof(B));
// ...
thatSameFile.read((char*)b, sizeof(B));
b->foo(); // error here
}
我发现了什么:
经过几个小时的调试,我发现__vfptr
的{{1}}成员(据我所知,是指向该对象的虚拟表的指针)正在改变之后文件读取语句。我想我不仅将b
的数据写入文件并将其复制到a
,我还将b
的虚拟表指针复制到a
。
我说的是对的吗?我该如何解决这个问题?
答案 0 :(得分:2)
我说的是对的吗?
不,这不对。问题的根源在于您只是将地址写入文件并将其加载回来(另外,使用了错误的大小)。
file.write((char*)&a, sizeof(B));
前一行将存储在变量a
中的指针以class B
的大小写入文件。
指针无法从文件重建,因为它们需要进行内存管理(在您的情况下是动态分配)。
所以声明
thatSameFile.read((char*)&b, sizeof(B));
只是用一些任意的,无意义的值覆盖存储在b
中的指针,加上堆栈上的一些额外字节。这基本上是未定义的行为。
至于your comment,这是一个错字;对于我上面写的内容,它不会有太大变化。指针无法从文件重建。
如何解决此问题?
如果需要编写结构/类的二进制图像。您可以为普通的POD类型(如
)执行此操作struct Foo {
char c;
int i;
double d;
long arrlong[25];
};
只包含整数类型,或整数类型的固定大小数组。
这些类型可以“安全地”写入并从同一目标体系结构的二进制文件中恢复(参见Endianness):
Foo a;
file.write((const char*)&a, sizeof(Foo));
// ...
Foo b;
thatSameFile.read((char*)&b, sizeof(Foo));
此外,您不能使用具有虚拟多态继承的类型。只是重新加载一个 vtable (它甚至没有被C ++标准指定)不足以告诉运行时安全的实际底层类型。
您应该查找序列化/反序列化以实现您想要的效果。有几个库可以很好地支持二进制格式,例如boost::serialization
或google protocol buffers,它们可以帮助您构建比POD序列化/反序列化更复杂的东西。