Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dereferencing a null pointer within typeid

Tags:

c++

typeid

While researching a recent question, I came upon the following clause in the '03 standard[1]:

When typeid is applied to an lvalue expression whose type is a polymorphic class type (10.3), the result refers to a type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the lvalue refers. If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws the bad_typeid exception (18.5.3).

Specifically, I am wondering about the last bit, which provides well defined behavior for the result of dereferencing a null pointer. As far as I can tell, this is the only time this is done[2]. Specifically, dynamic_cast<T&> has no special treatment for this case, and that seems like a much more useful scenario. Doubly so considering dynamic_cast<T&> is already defined as throwing an exception under certain circumstances.

Is there a specific reason that this particular expression was given special treatment? It seems completely arbitrary, so I am guessing there is some specific use case they had in mind.


[1] A similar clause exists in '11, but it refers to glvalue expressions, rather than lvalue expressions.

[2] delete 0; and dynamic_cast<T*>(0) come close, but in both cases you are dealing with a pointer value, rather than an actual object.

like image 904
Dennis Zickefoose Avatar asked May 17 '11 08:05

Dennis Zickefoose


People also ask

Can you dereference a null pointer?

Null dereferencing Because a null pointer does not point to a meaningful object, an attempt to dereference (i.e., access the data stored at that memory location) a null pointer usually (but not always) causes a run-time error or immediate program crash. In C, dereferencing a null pointer is undefined behavior.

What happens when dereferencing a null pointer?

Dereferencing a null pointer always results in undefined behavior and can cause crashes. If the compiler finds a pointer dereference, it treats that pointer as nonnull. As a result, the optimizer may remove null equality checks for dereferenced pointers.

Is Dereferencing null undefined?

According to ISO C++, dereferencing a null pointer is undefined behaviour.

What does it mean to dereference a null object?

A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid, but is NULL, typically causing a crash or exit. Extended Description. NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.


1 Answers

Had I paid more attention to the very next clause (5.2.8/3), I would have seen this

When typeid is applied to an expression other than an lvalue of a polymorphic class type, . . . The expression is not evaluated.

In other words, as with sizeof (among others in C++11), the compiler is not meant to actually run the code you pass to typeid, it is just supposed to analyze it for behavior. Unfortunately, unlike sizeof, the result sometimes depends on the runtime behavior of the expression due to polymorphic types.

Base* p1 = new Derived;
Base* p2 = new Base;
typeid(*p1); //equivalent to typeid(Derived) [assuming Base is polymorphic]
typeid(*p2); //equivalent to typeid(Base)

If the expression were completely unevaluated, the compiler could not check the RTTI to see that p1 is actually pointing to a Derived instead of a Base. The standard writers decided to go one step further, however, and stated that if the expression is ultimately a dereference of a pointer type, the compiler should only partially evaluate it. If the pointer is null, throw std::bad_typeid rather than perform the dereference and introduce undefined behavior.

Contrast that with dynamic_cast. The expression passed to dynamic_cast is always fully evaluated, the result would make no sense otherwise. Since the compiler is required to fully evaluate the expression anyhow, it makes no sense to instruct it to stop early and throw an exception.

In short, this is given special treatment in much the same way that sizeof(*(int*)0) is given special treatment. *(int*)0 isn't meant to be evaluated, so there is no reason to introduce undefined behavior in the first place, even though it looks bad.

like image 77
2 revs Avatar answered Sep 28 '22 18:09

2 revs