1

问题:

我使用二进制模式将对象写入文件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成员b(据我所知,它是指向该对象的虚拟表的指针)在文件读取语句之后发生了变化。我想我不仅将数据写入a到文件并将它们复制到,我还复制了到b的虚拟表指针。ab

我说的对吗?我该如何解决这个问题?

4

1 回答 1

2

我说的对吗?

不,这是不正确的。问题的根源在于您只是将地址写入文件并将它们加载回来(此外,使用了错误的大小)。

file.write((char*)&a, sizeof(B));

前一行将存储在变量中的指针写入a文件class B中。

无法从文件中重建指针,因为它们需要进行内存管理(在您的情况下为动态分配)。

所以声明

thatSameFile.read((char*)&b, sizeof(B));

b只是用一些任意的、无意义的值以及堆栈上的一些额外字节覆盖存储在其中的指针。这基本上是未定义的行为

至于您的评论,这是一个错字;我上面写的内容不会有太大变化。不能从文件中重建指针。


我该如何解决这个问题?

如果您需要编写结构/类的二进制图像。您可以对普通的 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::serializationgoogle协议缓冲区,它们可以帮助您构建比 POD 序列化/反序列化更复杂的东西。

于 2017-07-03T20:06:18.667 回答