Sunday, December 25, 2005

Fun with casts

Chris and I were discussing casting this morning…in particular consider this:

class B1 {
public:
int b1_var;
};
class B2 {
public:
int b2_var;
};
class D : public B1, public B2 {
public:
int d_var;
};

This is an example of multiple inheritence. Now for a quiz:

D * d_obj = new D;
void * v = d_obj;
B1 * b1 = (B1 *) v;
B2 * b2 = (B2 *) v;

Now the question is: which of these pointers is safe to use? The answer is: on the compiler I tested (CodeWarrior 8) and probably a number of others, b1 is safe, b2 is not. But I wouldn’t trust b1 either - the programming techique isn’t a good idea!

The problem is that the address of an object and its base sub-object can only be the same for one base of a class. You can think of the C++ compiler as doing something like this:

class D {
int b1_var;
int b2_var;
int d_var;
};

In other words, it stacks up the memory layout of the derived class with the bases inside it so that they are equivalent. But clearly B1 and B2 can’t both have that handy first memory slot in D. The compiler handles this for you by automatically changing the pointer value when you cast. For example:

B1 * b1 = d; // This is safe!
B2 * b2 = d;

This is perfectly legal - in CodeWarrior the first statement assigns the address of D to b1. The second statement adds a few bytes to D and assigns it to b2, such that b2 points to the B2 subobject within D.

The reason the cast from a void * fails is that when casting between a class and an untyped pointer, the compiler cannot correctly adjust the pointer for inheritence. The result is that b1 and b2 have the same actual value as d - no cast involving a void * will change the address.

Making things even more confusing, the cast operator in C++ does 3 different casts. C++ provides explicit named versions: const_cast, reinterpret_cast, static_cast (and dynamic_cast, but this behavior is never available via a C-style cast). I recommend using the C++ casts since they make it clear what the code intends to do and makes you the programmer think about which cast you really want. (C casts are static casts when possible, but reinterpret_casts otherwise…very dangerous!)

The moral of the story is: if you are going to cast to void *, be sure to cast back to the exact same type!

No comments:

Post a Comment