c++undefined-behaviorvirtual-functionsvptr

Could not call virtual member function after read object from file


Problem:

I wrote an object into a file in binary mode using std::fstream. However, when I read it back from that file to another object and then call one of the virtual member functions of this new object, there's a memory access violation error.

Here's what the code looks like:

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

}

What I've figured out:

After a couple of hours debugging, I see that the __vfptr member of b (which, as far as I know, is the pointer to the virtual table of that object) is changing after the file read statement. I guess that not only did I write the data of a to file and copy those to b, I also copied the virtual table pointer of a to b.

Is what I said correct? How can I fix this problem?


Solution

  • Is what I said correct?

    No, it's not correct. The source of the problem is that you are merely writing addresses to a file and loading them back (additionally, with wrong sizes used).

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

    The preceding line writes the pointer that was stored in the variable a with the size of class B to the file.

    Pointers cannot be reconstructed from a file, since they need to be memory-managed (dynamic allocation, in your case).

    So the statement

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

    just overwrites the pointer stored in b with some arbitrary, meaningless value, plus some additional bytes on the stack. This is basically undefined behavior.

    As for your comment, this was a typo; it wouldn't change much about what I wrote above. Pointers cannot be reconstructed from files.


    How can I fix this problem?

    If you need to write binary images of your structs / classes. You can do so for plain POD types like

    struct Foo {
        char c;
        int i;
        double d;
        long arrlong[25];
    };
    

    that contain only integral types, or fixed size arrays of integral types.

    Such types could be written "safely" to and restored from a binary file for the same target architecture (see Endianness):

    Foo a;
    
    file.write((const char*)&a, sizeof(Foo));
    
    // ...
    
    Foo b;
    thatSameFile.read((char*)&b, sizeof(Foo));
    

    Also you cannot use types with virtual polymorphic inheritance for doing so. Just reloading a vtable (which isn't even specified by the C++ standard) isn't enough to tell the runtime what's actually the underlying type safely.


    You should lookup serialization/deserialization to achieve what you want. There are several libraries that support binary formats well, like boost::serialization or google protocol buffers, which help you to build something more sophisticated than POD serialization/deserialization.