tags:

views:

259

answers:

5

Q1. Why does using NULL pointers with static_cast cause crashes while dynamic_cast and reinterpret_cast give a NULL pointer in return?

The problem occurred in a method similar to the one given below:

void A::SetEntity(B* pEntity, int iMyEntityType)
{   
    switch (iMyEntityType)
    { 
    case ENTITY1:
     {
      Set1(static_cast<C*>(pEntity));
      return;
     }
    case ENTITY2:
     {
      Set2(static_cast<D*>(pEntity));
      return;
     }
    case ENTITY3:
     {
      Set3(static_cast<E*>(pEntity));
      return;
     } 
    }
}

Inheritance:
  class X: public B
  class Y: public B
  class Z: public B

  class C: public X, public M
  class D: public Y, public M
  class E: public Z, public M

Q2. Is static_casting from B to C/D/E valid? (this worked ok till the input became NULL)

I'm using gcc version 3.4.3

+1  A: 

MyClass* p = static_cast<MyClass*>(0) works well.

New:

If you use multiple inheritance then static_cast may shift your pointer. Consider the following code:

struct B1 {};
struct B2 {};

struct A : B2, B1 {
 virtual ~A() {}
};

What is struct A? A contains a table of virtual functions and B1 and B2. B1 is shifted with respect to A. To cast B1 to A compiler need to back shift.

If the pointer to B1 is NULL then shift gives invalid result.

Alexey Malistov
+5  A: 

You can static_cast a null pointer - it will give you a null pointer.

In your snippet the problem is most possibly that you pass inconsistent values of pEntity and iMyEntityType into the function. So that when static_cast is done it blindly casts to the wrong type (not the same type as the actual object) and you get an invalid pointer that is later passed down the call stack and causes undefined behaviour (crashes the program). dynamic_cast in the same case sees that the object is really not of the expected type and returns a null pointer.

sharptooth
It is possible, although in that scenario the crash would occur more frequently and not for NULL values. I've done extensive testing on this and it worked properly. The failure occurred when pEntity was set to null. I've changed the casts to "dynamic_cast" and it solved the null pointer crash :(I thought that it was something to do with the way null pointers were handled in different cast types
Gayan
Do you have a complete snippet that demonstrates the problem? Something like 100 lines of a program with main() that can be compiled at any computer and show the problem.
sharptooth
There is no clas type specific null pointer handling.
anon
@sharptooth - Tried out a sample with trivial classes but could not recreate the crash. I'll keep working on it
Gayan
+1  A: 

static_cast is for situations when you know the cast can be done (either you cast to a parent class, or you have other ways of assessing the type of the class). There is no runtime check on the type (hence the static). On the other hand, dynamic_cast will check, at runtime, if the object is really of the type you want to cast it to. As for reinterpret_cast, it doesn't do anything but using the same memory for a different purpose. Note that reinterpret_cast should never be used to change from one class to another.

In the end, the reason static_cast on NULL pointer crashes, is because a static_cast with inheritance might requires a bit of pointer arithmetic form the compiler. This depend on how the compiler actually implement inheritance. But in case of multiple inheritance, it doesn't have a choice.

One way to see this is that the daughter class "contains" the parent class. It virtual table contains the one of the parent, but with added features. If the features are added at the beginning, then any cast to the parent class will point to a different place ... from where the features of the daughter class cannot be seen. I hope this make sense.

Note on pointer arithmetic

First, this is always be the case for multiple inheritance, but a compiler might choose to do so for single inheritance too.

Basically, if you look at the memory layout for an object content with virtual methods, you could do something like:

+---------------+----------------+
| ptr to vtable | members   .... |
+---------------+----------------+

In case of single inheritance, this is pretty much enough. In particular, you can ensure that the vtable of any derived class starts with the vtable of the mother class and the first members are those of the mother class.

Now, if you have multiple inheritance, things are more complex. In particular, you probably can't merge vtables and members in a consistent way (at least not in the general case). So, say you inherit from classes A, B and C, you will probably have something like:

                       A                       B                      C
+----------------------+-----------+-----------+----------+-----------+-----+
| local vtable/members | vtable A  | members A | vtable B | members B | ... |
+----------------------+-----------+-----------+----------+-----------+-----+

Such that if you point on A, you will see the object as an object of type A, plus the rest. But if you want to see the object as being of type B, you need to point to the address B, etc. Note, this might not be exactly what the system does, but that's the git of it.

PierreBdR
"static_cast with inheritance might requires a bit of pointer arithmetic"Could you please elaborate on this? I think this might be the possible cause since the inheritance tree of the objects I'm using is a bit complex (more complex than in the given sample)
Gayan
+3  A: 

static_cast cannot itself cause a crash - its behaviour at runtime is the same as reinterpret_cast. There is something wrong somewhere else in your code.

anon
Actually, that is not true ! `reinterpret_cast` might move the pointer!
PierreBdR
Not at run time.
anon
@PierreBdR - the other way around. `static_cast<>` may adjust the pointer (the base object portion of a derived class might not be at offset 0 of the derived class) .
Michael Burr
+1  A: 

What compiler are you using? A static cast from a base type to a derived type might result in an adjustment to the pointer - especially likely if multiple inheritance is involved (which doesn't seem to be the case in your situation from your description). However, it's still possible without MI.

The standard indicates that if a null pointer value is being cast that the result will be a null pointer value (5.2.9/8 Static cast). However, I think that on many compilers most downcasts (especially when single inheritance is involved) don't result in a pointer adjustment, so I could imagine that a compiler might have a bug such that it wouldn't make the special check for null that would be required to avoid 'converting' a zero value null pointer to some non-zero value senseless pointer. I would assume that for such a bug to exist you must be doing something unusual to get the compiler to have to adjust the pointer in the downcast.

It might be interesting to see what kind of assembly code was generated for your example.

And for detailed information about how a compiler might layout an object that might need pointer adjustment with static casts, Stan Lippman's "Inside the C++ Object Model" is a great resource.

Stroustrup's paper on Multiple Inheritance for C++ (from 1989) is also a good read. It's too bad if a C++ compiler has a bug like I speculate about here - Stroustrup discusses the null pointer issue explicitly in that paper (4.5 Zero Valued Pointers).

For your second question:

Q2. Is static_casting from B to C/D/E valid?

This is perfectly valid as long as when you perform the cast of the B pointer to a C/D/E pointer the B pointer is actually pointing to the B sub-object of a C/D/E object (respectively) and B isn't a virtual base. This is mentioned in the same paragraph of the standard (5.2.9/8 Static cast). I've highlighted the sentences of the paragraph most relevant to your questions:

An rvalue of type “pointer to cv1 B”, where B is a class type, can be converted to an rvalue of type “pointer to cv2 D”, where D is a class derived (clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The null pointer value (4.10) is converted to the null pointer value of the destination type. If the rvalue of type “pointer to cv1 B” points to a B that is actually a sub-object of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the result of the cast is undefined.

As a final aside, you can workaround the problem using something like:

Set1(pEntity ? static_cast<C*>(pEntity) : 0);

which is what the compiler should be doing for you.

Michael Burr
In the actual case, the objects were multiply inherited. Sorry about not mentioning this earlier. I've updated the question with a rough structure of what the inheritance model looks like.
Gayan