从文件中读取对象后无法调用虚拟成员函数

时间:2017-07-03 19:40:54

标签: c++ undefined-behavior virtual-functions vptr

问题:

我使用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

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

1 个答案:

答案 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::serializationgoogle protocol buffers,它们可以帮助您构建比POD序列化/反序列化更复杂的东西。