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?
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.