Pointer Basics
Program variables are each stored at a difference address within our computer’s memory. Pointers can be used to access the contents of variables stored at specific memory addresses.
Table of Contents
- The Address-Of Operator
- The Indirection Operator
- What are Pointers?
- Defining and Initializing Pointers
- Redefining a Pointer
- Example: C-Array Pointer Navigation
- Pointers and Const
- Uninitialized Pointers
- Null Pointers
- Guarding Against Dereferencing a Null Pointer
- NULL and 0 for Null Pointers
- Pointers vs References
- Further Reading
The Address-Of Operator
When we store data in a variable, that data gets stored in our computer’s Random Access Memory (RAM).
We can access the memory address of any variable using the address-of operator &
:
The Indirection Operator
What can we do with the memory address of a variable? Not much without the indirection operator *
.
Using this operator, we can access the data stored at a particular memory address. This is sometimes called dereferencing.
What are Pointers?
The address-of and indirection operators aren’t too exciting on their own, but they are crucial for understand the concept of pointers.
A pointer is a type of variable used to store the memory address of another variable. In this way, a pointer “points to” another variable.
When people talk about C or C++ being confusing this opinion usually has something to do with pointers (and manual memory management).
🎵 Note:
The address-of operator &
returns a pointer, not a raw number for the memory address.
Defining and Initializing Pointers
We declare pointers by placing an asterisk after the data type in the declaration.
// Create an integer variable:
int wholeNumber{ 42 };
// Create a pointer to the integer variable:
int* numberPointer{ &wholeNumber };
// Access the value 42 by way of the variable:
std::cout << " wholeNumber: " << wholeNumber << "\n";
// Access the value 42 by dereferencing the pointer:
std::cout << " *numberPointer: " << *numberPointer << "\n";
// Change the value of wholeNumber using the pointer:
(*numberPointer)++; // wholeNumber is now 43
⚡ Warning:
The pointer definition use of an asterisk is different from the indirection operator.
Redefining a Pointer
After a pointer has been defined and initialized we can also change what it points to.
Example: C-Array Pointer Navigation
Many collections in C++ are implemented using pointers. Look back over our module on iterators and you’ll see how similar pointers are to iterators.
C-style arrays are also implemented using pointers and we can use what is call pointer arithmetic to access elements within an array.
⚡ Warning:
Pointer arithmetic can be dangerous if you point beyond array boundaries.
Pointers and Const
The concept of const
can be applied to pointers in a few different ways:
- Regular pointers cannot point to
const
variables:
const int answer{42};
int* answerPointer{&answer}; // COMPILE ERROR: Regular pointers can't point to const variables.
- Pointers can be made to point to
const
variables, but the pointer can be reassigned:
const int answer1{42};
const int answer2{999};
const int* answerPointer{&answer1}; // Okay! Note: const comes first.
answerPointer = &answer2; // Weird, but also fine!
(*answerPointer) = 12; // COMPILE ERROR: Cannot change a const value.
- Pointers can also be made
const
, meaning they cannot be changed after initialization:
int answer1{42};
int answer2{999};
int* const answerPointer{&answer1}; // Note: const comes after type.
answerPointer = &answer2; // COMPILE ERROR: Const pointers cannot be reassigned.
Uninitialized Pointers
Unassigned pointers, sometimes called wild pointers contain what is known as a garbage address.
double e{2.71828};
double* validPointer{&e}; // Points to the memory address of the 'e' variable.
double* garbagePointer; // Uninitialized. Contains a garbage address.
💡 Best Practice:
Dereferencing a wild pointer is undefined behaviour and should be avoided.
Null Pointers
There is a special literal value nullptr
we can assign to pointers to indicate that they are uninitialized.
int* nullPointer1{nullptr}; // Manually made null using the nullptr literal.
int* nullPointer2{}; // An empty initializer will also create null pointers.
Previously assigned pointers can also be made null.
double e{2.71828};
double* validPointer{&e}; // Points to the memory address of the 'e' variable.
validPointer = nullptr; // Previously valid pointers can be made null.
💡 Best Practice:
Uninitialized pointers should always be explicitly made into null pointers.
Guarding Against Dereferencing a Null Pointer
Dereferencing a null pointer is undefined behaviour.
int* pointer{}; // Creates a null pointer
std::cout << *pointer; // UNDEFINED BEHAVIOUR!
As such, we should always guard pointer access with a boolean test.
// METHOD #1:
if (pointer != nullptr) {
// pointer isn't null, so we can access it:
std::cout << *pointer;
}
// METHOD #2:
if (pointer) { // Non-null pointers are "truthy" while nullptr is "falsey".
// pointer isn't null, so we can access it:
std::cout << *pointer;
}
⚡ Warning:
Pointers that evaluates as true aren’t guaranteed to point to a valid memory location.
NULL and 0 for Null Pointers
In legacy code you will often see the number 0
or the preprocessor macro NULL
used in place of nullptr
. Subtle bugs can be triggered when using 0
or NULL
.
💡 Best Practice:
Always use nullptr
when using C++ 11 or greater, even when updating legacy code.
Pointers vs References
At this point you might be wondering why C++ includes both pointers and references when they appear to offer nearly identical functionality.
The short answer: C++ inherited pointers from C and references were added later.
References should be preferred over pointers because:
- Reference syntax is cleaner and pointer syntax. (No need for the indirection and address-of operators.)
- References are safer than pointers as they must always refer to a valid variable. (Unlike pointers there is no such thing as an uninitialized or null reference.)
🎵 Note:
In legacy code (or in Unreal Engine C++) pointers are everywhere and cannot be avoided.
Further Reading
The learncpp.com website goes into great detail on all thing pointer related, specifically their sections: