Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is dereferencing of nullptr while using a static method not undefined behaviour in C++?

I was reading a post on some nullptr peculiarities in C++, and a particular example caused some confusion in my understanding.

Consider (simplified example from the aforementioned post):

struct A {   
    void non_static_mem_fn() {}  
    static void static_mem_fn() {}  
};


A* p{nullptr};

/*1*/ *p;
/*6*/ p->non_static_mem_fn();
/*7*/ p->static_mem_fn();

According to the authors, expression /*1*/ that dereferences the nullptr does not cause undefined behaviour by itself. Same with expression /*7*/ that uses the nullptr-object to call a static function.

The justification is based on issue 315 in C++ Standard Core Language Closed Issues, Revision 100 that has

...*p is not an error when p is null unless the lvalue is converted to an rvalue (7.1 [conv.lval]), which it isn't here.

thus making a distinction between /*6*/ and /*7*/.

So, the actual dereferencing of the nullptr is not undefined behaviour (answer on SO, discussion under issue 232 of C++ Standard, ...). Thus, the validity of /*1*/ is understandable under this assumption.

However, how is /*7*/ guaranteed to not cause UB? As per the cited quote, there is no conversion of lvalue to rvalue in p->static_mem_fn();. But the same is true for /*6*/ p->non_static_mem_fn();, and I think my guess is confirmed by the quote from the same issue 315 regarding:

/*6*/ is explicitly noted as undefined in 12.2.2 [class.mfct.non-static], even though one could argue that since non_static_mem_fn(); is empty, there is no lvalue->rvalue conversion.

(in the quote, I changed "which" and f() to get the connection to the notation used in this question).


So, why is such a distinction made for p->static_mem_fn(); and p->non_static_mem_fn(); regarding the causality of UB? Is there an intended use of calling static functions from pointers that could potentially be nullptr?


Appendix:

  • this question asks about why dereferencing a nullptr is undefined behaviour. While I agree that in most cases it is a bad idea, I do not believe the statement is absolutely correct as per the links and quotes here.
  • similar discussion in this Q/A with some links to issue 232.
  • I was not able to find a question devoted to static methods and the nullptr dereferencing issue. Maybe I missed some obvious answer.
like image 870
Anton Menshov Avatar asked Aug 09 '20 22:08

Anton Menshov


People also ask

Why is dereferencing a null pointer undefined behavior?

Null dereferencing In C, dereferencing a null pointer is undefined behavior. Many implementations cause such code to result in the program being halted with an access violation, because the null pointer representation is chosen to be an address that is never allocated by the system for storing objects.

Is Dereferencing null undefined?

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

Can a nullptr be dereferenced?

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. NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.

Does nullptr have an address?

nullptr is a (literal) constant, and these don't have a memory address, like any other literal constant in your code.


1 Answers

Standard citations in this answer are from the C++17 spec (N4713).

One of the sections cited in your question answers the question for non-static member functions. [class.mfct.non-static]/2:

If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

This applies to, for example, accessing an object through a different pointer type:

std::string foo;

A *ptr = reinterpret_cast<A *>(&foo); // not UB by itself
ptr->non_static_mem_fn();             // UB by [class.mfct.non-static]/2

A null pointer doesn't point at any valid object, so it certainly doesn't point to an object of type A either. Using your own example:

p->non_static_mem_fn(); // UB by [class.mfct.non-static]/2

With that out of the way, why does this work in the static case? Let's pull together two parts of the standard:

[expr.ref]/2:

... The expression E1->E2 is converted to the equivalent form (*(E1)).E2 ...

[class.static]/1 (emphasis mine):

... A static member may be referred to using the class member access syntax, in which case the object expression is evaluated.

The second block, in particular, says that the object expression is evaluated even for static member access. This is important if, for example, it is a function call with side effects.

Put together, this implies that these two blocks are equivalent:

// 1
p->static_mem_fn();

// 2
*p;
A::static_mem_fn();

So the final question to answer is whether *p alone is undefined behavior when p is a null pointer value.

Conventional wisdom would say "yes" but this is not actually true. There is nothing in the standard that states dereferencing a null pointer alone is UB and there are several discussions that directly support this:

  • Issue 315, as you have mentioned in your question, explicitly states that *p is not UB when the result is unused.
  • DR 1102 removes "dereferencing the null pointer" as an example of UB. The given rationale is:

    There are core issues surrounding the undefined behavior of dereferencing a null pointer. It appears the intent is that dereferencing is well defined, but using the result of the dereference will yield undefined behavior. This topic is too confused to be the reference example of undefined behavior, or should be stated more precisely if it is to be retained.

  • This DR links to issue 232 where it is discussed to add wording that explicitly indicates *p as defined behavior when p is a null pointer, as long as the result is not used.

In conclusion:

p->non_static_mem_fn(); // UB by [class.mfct.non-static]/2
p->static_mem_fn();     // Defined behavior per issue 232 and 315.
like image 119
cdhowie Avatar answered Oct 05 '22 00:10

cdhowie