In the previous lesson (12.7 -- Introduction to pointers), we covered the basics of pointers, which are objects that hold the address of another object. This address can be dereferenced using the dereference operator (*) to get the object at that address:
The above example prints:
5 5
In the prior lesson, we also noted that pointers do not need to point to anything. In this lesson, we’ll explore such pointers (and the various implications of pointing to nothing) further.
Null pointers
Besides a memory address, there is one additional value that a pointer can hold: a null value. A null value (often shortened to null) is a special value that means something has no value. When a pointer is holding a null value, it means the pointer is not pointing at anything. Such a pointer is called a null pointer.
The easiest way to create a null pointer is to use value initialization:
Best practice
Value initialize your pointers (to be null pointers) if you are not initializing them with the address of a valid object.
Because we can use assignment to change what a pointer is pointing at, a pointer that is initially set to null can later be changed to point at a valid object:
The nullptr keyword
Much like the keywords true and false represent Boolean literal values, the
nullptr keyword represents a null pointer literal. We can use nullptr to
explicitly initialize or assign a pointer a null value.
In the above example, we use assignment to set the value of ptr2 to nullptr, making
ptr2 a null pointer.
Best practice
Use nullptr when you need a null pointer literal for initialization, assignment, or passing a
null pointer to a function.
Dereferencing a null pointer results in undefined behavior
Much like dereferencing a dangling (or wild) pointer leads to undefined behavior, dereferencing a null pointer also leads to undefined behavior. In most cases, it will crash your application.
The following program illustrates this, and will probably crash or terminate your application abnormally when you run it (go ahead, try it, you won’t harm your machine):
Conceptually, this makes sense. Dereferencing a pointer means “go to the address the pointer is pointing at and access the value there”. A null pointer holds a null value, which semantically means the pointer is not pointing at anything. So what value would it access?
Accidentally dereferencing null and dangling pointers is one of the most common mistakes C++ programmers make, and is probably the most common reason that C++ programs crash in practice.
Warning
Whenever you are using pointers, you’ll need to be extra careful that your code isn’t dereferencing null or dangling pointers, as this will cause undefined behavior (probably an application crash).
Checking for null pointers
Much like we can use a conditional to test Boolean values for true or false, we can
use a conditional to test whether a pointer has value nullptr or not:
The above program prints:
ptr is non-null nullPtr is null
In lesson 4.9 -- Boolean values, we noted
that integral values will implicitly convert into Boolean values: an integral value of 0 converts
to Boolean value false, and any other integral value converts to Boolean value true.
Similarly, pointers will also implicitly convert to Boolean values: a null pointer converts to Boolean value
false, and a non-null pointer converts to Boolean value true. This allows us to skip
explicitly testing for nullptr and just use the implicit conversion to Boolean to test whether a
pointer is a null pointer. The following program is equivalent to the prior one:
Warning
Conditionals can only be used to differentiate null pointers from non-null pointers. There is no convenient way to determine whether a non-null pointer is pointing to a valid object or dangling (pointing to an invalid object).
Use nullptr to avoid dangling pointers
Above, we mentioned that dereferencing a pointer that is either null or dangling will result in undefined behavior. Therefore, we need to ensure our code does not do either of these things.
We can easily avoid dereferencing a null pointer by using a conditional to ensure a pointer is non-null before trying to dereference it:
But what about dangling pointers? Because there is no way to detect whether a pointer is dangling, we need to
avoid having any dangling pointers in our program in the first place. We do that by ensuring that any pointer
that is not pointing at a valid object is set to nullptr.
That way, before dereferencing a pointer, we only need to test whether it is null -- if it is non-null, we assume the pointer is not dangling.
Best practice
A pointer should either hold the address of a valid object, or be set to nullptr. That way we only need to test pointers for null, and can assume any non-null pointer is valid.
Unfortunately, avoiding dangling pointers isn’t always easy: when an object is destroyed, any pointers to that
object will be left dangling. Such pointers are not nulled automatically! It is the programmer’s
responsibility to ensure that all pointers to an object that has just been destroyed are properly set to
nullptr.
Warning
When an object is destroyed, any pointers to the destroyed object will be left dangling (they will not be
automatically set to nullptr). It is your responsibility to detect these cases and ensure those
pointers are subsequently set to nullptr.
Legacy null pointer literals: 0 and NULL
In older code, you may see two other literal values used instead of nullptr.
The first is the literal 0. In the context of a pointer, the literal 0 is specially
defined to mean a null value, and is the only time you can assign an integral literal to a pointer.
As an aside…
On modern architectures, the address 0 is typically used to represent a null pointer. However,
this value is not guaranteed by the C++ standard, and some architectures use other values. The literal
0, when used in the context of a null pointer, will be translated into whatever address the
architecture uses to represent a null pointer.
Additionally, there is a preprocessor macro named NULL (defined in the <cstddef> header).
This macro is inherited from C, where it is commonly used to indicate a null pointer.
Both 0 and NULL should be avoided in modern C++ (use nullptr instead).
We discuss why in lesson 12.11 -- Pass
by address (part 2).
Favor references over pointers whenever possible
Pointers and references both give us the ability to access some other object indirectly.
Pointers have the additional abilities of being able to change what they are pointing at, and to be pointed at null. However, these pointer abilities are also inherently dangerous: A null pointer runs the risk of being dereferenced, and the ability to change what a pointer is pointing at can make creating dangling pointers easier:
Since references can’t be bound to null, we don’t have to worry about null references. And because references must be bound to a valid object upon creation and then can not be reseated, dangling references are harder to create.
Because they are safer, references should be favored over pointers, unless the additional capabilities provided by pointers are required.
Best practice
Favor references over pointers unless the additional capabilities provided by pointers are needed.
A joke
Did you hear the joke about the null pointer?
That’s okay, you wouldn’t get dereference.