Friday, May 24, 2013

Class Pointers and uintptr_t

In the last few months that I've been doing C++ programming again, I've run into some bugs in my code that were quite tricky to figure out. There are lots of opportunities for bugs in C++, like buffer over-runs and accessing an object that's been deleted, but this bug below was trickier than usual....

uintptr_t is a handy type, because it's an unsigned integer that's the same size as a pointer on whatever platform you're compiling for.  Therefore, a variable of type uintptr_t can hold an integer or a pointer. How I'm using uintptr_t in my engine is a good subject for a different post, but for now, let's consider a tricky bug that comes up when using them to store memory addresses.

class Foo { ... };
class Bar : public Foo { ... };

Bar b;
uintptr_t address = &b;

Foo *f = reinterpret_cast<Foo*>(address);
// at this point, *f is full of garbage
// even though f points to b

Of course, this works fine:

Foo *f = &b;
// *f is correct

I was having problems with the contents of objects being garbage even though I confirmed in the debugger that the memory address was exactly the same as when the object's address was converted to a uintptr_t.

After some googling, I found that what I was seeing was expected behavior for class pointers.  You can only safely convert a class pointer to and from a void* or unitptr_t if the type of the reinterpret_cast to get the pointer back is exactly the same as the type that was initially converted to a uintptr_t.

So this works:

Bar b;
uintptr_t address = &b;
Foo *f = static_cast<Foo*>(reinterpret_cast<Bar*>(address));

That's a ridiculous example, of course, but it illustrates the point. If you use void*'s or uintptr_t's to store class pointers, it's possible you might run into this behavior.


2 comments:

  1. Did you add a virtual method to class Bar? That's one cause (and the only one I can think of) that would shift the address of Foo, because the first bytes become the vtable pointer of Bar rather than the first member variable of Foo. C++ inheritance embeds the base class into the derived class, sandwiched between the leading vtable pointer and trailing derived class fields, rather than purely extending bytes to the base class. So, if you intend to add virtual methods to a derived class, just go ahead and add a virtual destructor to the base class. This ensures polymorphic deletion is done correctly no matter which pointer is used, and you don't get pointer garbarge either when casting pointers - at least for single inheritance.

    ReplyDelete
  2. This entry saved my butt. I'd been trying to track down the segfault bug with my memory pool allocator for days!

    ReplyDelete