What happens when nullptr dereference?

  • If you are given an exhaustive answer, mark it as correct (a daw opposite the selected answer). - Nicolas Chabanovsky

2 answers 2

The nullptr nullptr is undefined behavior.

This means that the compiler has the right to assume that this situation will never happen, and handle it as he pleases. It follows that if your program has nullptr dereference, then the compiler has the right to produce any code, there are no obligations to you or there are no guarantees.

At best, your program just crashes. At the worst - will behave in an inexplicable strange way.


Here is an example simplified from the real critical bug in the Linux kernel [GNU / Linux, as RMS says]. (Stolen from here .)

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

In this example, the code seems to check for a pointer to nullptr . If the optimizer first removes the dead code , and then deletes the meaningless checks , our code will turn into this:

 // после удаления мёртвого кода void contains_null_check_stage1(int *P) { //int dead = *P; // неиспользуемая переменная, убрано оптимизатором if (P == 0) return; *P = 4; } 

and then:

 // после удаления бессмысленных проверок void contains_null_check_stage2(int *P) { if (P == 0) // проверка на null имеет смысл, оставляем. return; *P = 4; } 

But if the optimizer is implemented differently, and performs the optimization in a different order, we get the following:

 // после удаления бессмысленных проверок void contains_null_check_stage1(int *P) { int dead = *P; if (false) // в этой точке P был разыменован, он не может быть nullptr return; *P = 4; } 

and then:

 // после удаления мёртвого кода void contains_null_check_stage2(int *P) { //int dead = *P; //if (false) // return; *P = 4; } 

What happened in the first stage? For the compiler, dereferencing nullptr has undefined behavior, it has the right to assume that this does not happen. So, seeing the line int dead = *P; He has the right to assume that P not nullptr . Therefore, it can throw out a nullptr check as meaningless.

For many normal programmers, removing the check for nullptr from this function looks very strange (and they will probably even send a bug to the compiler developers). However, both compilation options are 100% standard compliant, and each of these optimizations is very important for performance.

Despite the fact that this example seems too simple and far-fetched, such things often happen implicitly as a result of inline-function substitution: the substitution allows for greater optimization. This means that if the optimizer decides to include a function in another function, a large number of local optimizations are immediately included in the game, and this may change the behavior of the code. This is also allowed by the standard, and is very important for good performance of the compiled code.


The moral of this story is that the C compiler is no longer a “high-level assembler,” and the executable code can be very far from literal, line-by-line execution of what you wrote.

  • Not sure if the example is good. I think it is assumed that a kernel module with this code must be compiled with such compilation flags that exclude the optimization considered. I don’t know why, but apparently in the code that calls contains_null_check, it is required that with p == 0 some interrupt handler should work, and in a natural way, that is when it is called by hardware (I can assume that this is somehow related to the MMU, TLB, etc.) - avp
  • one
    @avp: This is one of the reasons why the kernel is compiled with -O2 :) - VladD
  • @avp: I'll try to find the bug itself. - VladD pm
  • @avp: It seems that this bug: lwn.net/Articles/341773 - VladD
  • I really did not understand this long discussion. It only made that most likely my assumption about calling the handler in such an extravagant way is incorrect. - avp 8:50 pm

nullptr is a std::nullptr_t . For this type, the operation of dereference is absent, therefore in the original formulation:

What happens when nullptr dereference?

the answer will be: compilation error , with the output of the corresponding message:

error: indirection requires pointer operand ('nullptr_t' invalid)

If we are talking about dereferencing a native pointer to some type T , which was previously assigned the value nullptr :

 T* p = nullptr; *p; 

then there is an implicit conversion from type std::nullptr_t to type T* , followed by std::nullptr_t . And such an action already leads to indefinite behavior .