Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Representing Absent Pointers with nullptr
Represent absent pointers with nullptr and check for validity before dereferencing.
Null Pointers
In the previous lesson, we covered pointer basics: objects holding addresses of other objects. This address can be dereferenced using the dereference operator (*) to get the object at that address:
#include <iostream>
int main()
{
int health{100};
std::cout << health << '\n'; // Print health's value
int* ptr{&health}; // ptr holds health's address
std::cout << *ptr << '\n'; // Use dereference to print value at ptr's address
return 0;
}
This example prints:
100
100
We also noted that pointers don't need to point to anything. In this lesson, we'll explore such pointers further.
Null Pointers
Besides a memory address, there's one additional value a pointer can hold: a null value. A null value (often shortened to null) is a special value meaning something has no value. When a pointer holds a null value, it means the pointer isn't pointing at anything. Such a pointer is called a null pointer.
The easiest way to create a null pointer is using value initialization:
int main()
{
int* ptr{}; // ptr is now a null pointer, not holding an address
return 0;
}
Best Practice
Value initialize your pointers (to be null pointers) if you're not initializing them with a valid object's address.
Because we can use assignment to change what a pointer points to, a pointer initially set to null can later point at a valid object:
#include <iostream>
int main()
{
int* ptr{}; // ptr is a null pointer
int health{100};
ptr = &health; // ptr now pointing at health (no longer null)
std::cout << *ptr << '\n'; // Print health's value through dereferenced ptr
return 0;
}
The nullptr Keyword
Just as 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:
int main()
{
int* ptr{nullptr}; // Can use nullptr to initialize a pointer to null
int score{100};
int* ptr2{&score}; // ptr2 is a valid pointer
ptr2 = nullptr; // Can assign nullptr to make the pointer null
someFunction(nullptr); // Can pass nullptr to a function with a pointer parameter
return 0;
}
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
Just 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.
This program illustrates this, and will probably crash when you run it:
#include <iostream>
int main()
{
int* ptr{}; // Create a null pointer
std::cout << *ptr << '\n'; // Dereference the null pointer
return 0;
}
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 isn't 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 C++ programs crash in practice.
Warning
Whenever you're using pointers, be extra careful that your code isn't dereferencing null or dangling pointers, as this will cause undefined behavior (probably a crash).
Checking for Null Pointers
Just as 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:
#include <iostream>
int main()
{
int health{100};
int* ptr{&health};
if (ptr == nullptr) // Explicit test for equivalence
std::cout << "ptr is null\n";
else
std::cout << "ptr is non-null\n";
int* nullPtr{};
std::cout << "nullPtr is " << (nullPtr==nullptr ? "null\n" : "non-null\n");
return 0;
}
This program prints:
ptr is non-null
nullPtr is null
Pointers implicitly convert to Boolean values: a null pointer converts to false, and a non-null pointer converts to true. This allows us to skip explicitly testing for nullptr:
#include <iostream>
int main()
{
int health{100};
int* ptr{&health};
// Pointers convert to Boolean false if null, and Boolean true if non-null
if (ptr) // Implicit conversion to Boolean
std::cout << "ptr is non-null\n";
else
std::cout << "ptr is null\n";
int* nullPtr{};
std::cout << "nullPtr is " << (nullPtr ? "non-null\n" : "null\n");
return 0;
}
Warning
Conditionals can only differentiate null pointers from non-null pointers. There's no convenient way to determine whether a non-null pointer is pointing to a valid object or dangling.
Use nullptr to Avoid Dangling Pointers
We need to ensure our code doesn't dereference null or dangling pointers.
We can avoid dereferencing a null pointer by using a conditional to ensure a pointer is non-null first:
// Assume ptr is some pointer that may or may not be null
if (ptr) // If ptr is not null
std::cout << *ptr << '\n'; // Valid to dereference
else
// Do something else that doesn't involve dereferencing ptr
But what about dangling pointers? Because there's no way to detect whether a pointer is dangling, we need to avoid having any dangling pointers in the first place. We do that by ensuring any pointer not pointing at a valid object is set to nullptr.
That way, before dereferencing a pointer, we only need to test whether it's null. If it's non-null, we assume the pointer isn't dangling.
Best Practice
A pointer should either hold a valid object's address, 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's the programmer's responsibility to ensure those pointers are set to nullptr.
Warning
When an object is destroyed, any pointers to the destroyed object will be left dangling (they won't be automatically set to nullptr). It's 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, literal 0 is specially defined to mean a null value:
int main()
{
float* ptr{0}; // ptr is now a null pointer (don't do this)
float* ptr2;
ptr2 = 0; // ptr2 is now a null pointer (don't do this)
return 0;
}
As an Aside
On modern architectures, address 0 is typically used to represent a null pointer. However, this value isn't guaranteed by the C++ standard.
Additionally, there's a preprocessor macro named NULL (defined in the
#include <cstddef> // for NULL
int main()
{
double* ptr{NULL}; // ptr is a null pointer
double* ptr2;
ptr2 = NULL; // ptr2 is now a null pointer
return 0;
}
Both 0 and NULL should be avoided in modern C++ (use nullptr instead). We discuss why in the lesson on 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're 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:
int main()
{
int* ptr{};
{
int health{100};
ptr = &health; // Assign the pointer to an object that will be destroyed
} // ptr is now dangling and pointing to invalid object
if (ptr) // Condition evaluates to true because ptr is not nullptr
std::cout << *ptr; // Undefined behavior
return 0;
}
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't be reseated, dangling references are harder to create.
Because they're 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.
Quiz
Question 1
1a) Can we determine whether a pointer is null or not? If so, how?
Answer: Yes, we can use a conditional (if statement or conditional operator) on the pointer. A pointer will convert to Boolean false if it's null, and true otherwise.
1b) Can we determine whether a non-null pointer is valid or dangling? If so, how?
Answer: There's no easy way to determine this.
Question 2
For each subitem, answer whether the action described will result in behavior that is: predictable, undefined, or possibly undefined. If the answer is "possibly undefined", clarify when.
2a) Assigning an object's address to a non-const pointer
Answer: Predictable. This just copies the address into the pointer object.
2b) Assigning nullptr to a pointer
Answer: Predictable.
2c) Dereferencing a pointer to a valid object
Answer: Predictable.
2d) Dereferencing a dangling pointer
Answer: Undefined.
2e) Dereferencing a null pointer
Answer: Undefined.
2f) Dereferencing a non-null pointer
Answer: Possibly undefined, if the pointer is dangling.
Question 3
Why should we set pointers that aren't pointing to a valid object to nullptr?
Answer: We can't determine whether a non-null pointer is valid or dangling, and accessing a dangling pointer causes undefined behavior. Therefore, we need to ensure we don't have any dangling pointers in our program.
If we ensure all pointers are either pointing to valid objects or set to nullptr, then we can use a conditional to test for null to ensure we don't dereference a null pointer, and assume all non-null pointers are pointing to valid objects.
Representing Absent Pointers with nullptr - Quiz
Test your understanding of the lesson.
Practice Exercises
Null Pointers
Learn to work safely with null pointers. Understand nullptr, how to check for null pointers, and why you should always initialize pointers and avoid dangling pointers.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!