casting class ptr to void* and back causes crash

8c58c966787228871e085f7ffa0bdaeb
0
Remdul 101 Nov 23, 2009 at 20:35

I’m finding a strange problem when trying to cast some class pointer to void, and then back again, then call a function of the base class, and then try to call a virtual function of a derived class, resulting into access violation like this:

Unhandled exception at 0x004fcfd9 in fhx.exe: 0xC0000005: Access violation reading location 0x0000006f.

To me it seems that the v-table (at least for the virtual functions) of the class become corrupted when SetBaz() is called (after being cast from void pointer to base class).

The crash doesn’t seem to occur when the derived class function is made non-virtual. In fact, all virtual functions of the derived classes will crash, but the non-virtual functions are perfectly fine, as are all member variables. No traces of memory corruption.

// base class of AAA
class XXX
{
private:
 int baz;
protected:
 XXX() { baz = 666; }
public:
 void SetBaz(double v) { baz = v; }
};

// base class for BBB and CCC
class AAA: public XXX
{
private:
 int foo;
public:
 AAA() { int foo = 0; }
 void SetFoo(int v) { foo = v; }
 virtual void Hello() { printf(" (AAA::Hello) "); }
};

// derived class
class BBB: public AAA
{
public:
 BBB() {}
 void Hello() { printf(" (BBB::Hello) "); }
};

// derived class, without virtual function
class CCC: public AAA
{
public:
 CCC() {}
};

void Test()
{
 // set to false and all is good
 bool weWantToCrash = true;
 
 // create some instances of both derived classes
 BBB *b = new BBB;
 CCC *c = new CCC;
 
 // this causes no trouble
 b->SetBaz( 111 );
 c->SetBaz( 222 );
 
 // but this...
 if (weWantToCrash) {
  void *bptr = b;
  void *cptr = c;
  ((XXX*)bptr)->SetBaz( 111 ); // this seems to screw up the vtable of b
  ((XXX*)cptr)->SetBaz( 222 ); // this seems to screw up the vtable of c
  
  print("...but we're still running...\n");
 }
 
 // non virtual functions will work fine
 b->SetFoo(1);
 c->SetFoo(2);
 print("SetFoo no problem...\n");
 
 // now this will crash if weWantToCrash == true
 b->Hello();
 c->Hello();
 
 // just for kicks, perhaps casting to AAA will work?
 AAA *ptr = NULL;
 
 Console.Print("going to say hello to b...");
 ptr = b;
 ptr->Hello(); // nope, this will crash as well if weWantToCrash == true
 print("OK...\n");
 
 Console.Print("going to say hello to c...");
 ptr = c;
 ptr->Hello(); // nope, this will crash as well if weWantToCrash == true
 print("OK...\n");
 
 // looks like virtual functions are now screwed up!
}

This happens with MSVC2005E.

4 Replies

Please log in or register to post a reply.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Nov 23, 2009 at 21:42

Yep. The cause is that XXX has no virtual functions, so the compiler doesn’t include a vtable pointer in its layout. Then when AAA is laid out, the compiler places a vtable pointer first, then the contents of XXX, then the member variables of AAA. So, when you cast the BBB * (or an AAA *) to void * and then back to XXX *, you end up with an address 4 bytes less than what it should be. Then the SetBaz call stomps on the vtable pointer instead of setting baz.

You can see this by inserting the code

XXX * bptr = b;

You’ll see in the debugger that bptr is 4 bytes after b. On my machine, I got b == 0x3429c0, and bptr == 0x3429c4, for example.

The moral of the story: Bad Things can happen when you cast pointers to non-POD types through void *, and this should be avoided if at all possible.

8c58c966787228871e085f7ffa0bdaeb
0
Remdul 101 Nov 23, 2009 at 23:22

Aha!

Reedbeta, you make everything seem so simple. :)

I guess I will create an extra, empty base class (call it ZZZ) that XXX is derived from, and turn void* into ZZZ*. That would be a correct solution right? Would I have to worry about the compiler optimizing the empty class away?

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Nov 24, 2009 at 00:10

Yes, that should be fine. You could also simply use XXX as the base class if all the classes you care about are derived from it. No need to introduce an extra empty base class.

5225bc0c3bf66f4c275c332de6388d1f
0
SyntaxError 101 Nov 24, 2009 at 15:59

Also, in general try to avoid the unsafe casts. Use static_cast and dynamic_cast where you can. Only use () or reinterpret_cast where you absolutely have to.