Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NULL pointer compatibility with static_cast

Tags:

c++

casting

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

like image 501
Gayan Avatar asked Dec 09 '09 09:12

Gayan


2 Answers

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.

like image 109
sharptooth Avatar answered Sep 27 '22 21:09

sharptooth


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.

like image 39
Michael Burr Avatar answered Sep 27 '22 22:09

Michael Burr