8
votes

If I don't actually access the dereferenced "object", is dereferencing the null pointer still undefined?

int* p = 0;
int& r = *p;    // undefined?
int* q = &*p;   // undefined?

A slightly more practical example: can I dereference the null pointer to distinguish between overloads?

void foo(Bar&);
void foo(Baz&);

foo(*(Bar*)0);  // undefined?

Okay, the reference examples are definitely undefined behavior according to the standard:

a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior.

Unfortunately, the emphasized part is ambiguous. Is it the binding part that causes undefined behavior, or is the dereferencing part sufficient?

3
This must be an exact dupe. I know we have discussed this beore.Martin York

3 Answers

6
votes

I think the second opus of What every C programmer should know about Undefined Behavior might help illustrate this issue.

Taking the example of the blog:

void contains_null_check(int *P) {
  int dead = *P;
  if (P == 0)
    return;
  *P = 4;
}

Might be optimized to (RNCE: Redundant Null Check Elimintation):

void contains_null_check_after_RNCE(int *P) {
  int dead = *P;
  if (false)  // P was dereferenced by this point, so it can't be null 
    return;
  *P = 4;
}

Which is turn optimized into (DCE: Dead Code Elimination):

void contains_null_check_after_RNCE_and_DCE(int *P) {
  //int dead = *P; -- dead store
  //if (false)     -- unreachable branch
  //  return;
  *P = 4;
}

As you can see, even though dead is never used, the simple int dead = *P assignment has caused Undefined Behavior to creep in the program.

To distinguish between overloads, I'd suggest using a pointer (which might be null) rather than artificially creating a null reference and exposing yourself to Undefined Behavior.

5
votes
int& r = *p;    // undefined?

I think right here you've undefined behavior even if you don't actually use r (or *p)- the dereferenced object. Because after this step (i.e dereferencing the null pointer), the program behaviour is not guaranteed by the language, as the program may crash immediately which is one of the possibilities of UB. You seem to think that only reading the value of r so as to be used in real purpose invokes UB. I don't think so.

Also, the language specification clearly says "the effect of dereferencing the null pointer" invokes undefined behavior. It does not say "the effect of actually using dereferenced object from a null pointer" invokes UB. The effect of dereferencing the null pointer (or in other words undefined behavior) doesn't mean that you will necessarily and immediately get problems, or it must crash immediately after dereferencing the null pointer. No. It simply means, the program behavior is not defined after dereferencing the null pointer. That is, the program may run normally, as expected, from start to end. Or it may crash immediately, or after after some time - after few minutes, hours or days. Anything can happen anytime after dereferencing the null pointer.

4
votes

Yes it is undefined behavior, because the spec says that an "lvalue designates an object or function" (at clause 3.10) and it says for the *-operator "the result [of dereferencing] is an lvalue referring to the object or function to which the expression points" (at clause 5.3.1).

That means there is no description for what happens when you dereference a null pointer. It's simply undefined behavior.