I will explain the most feared concept in C++: pointers. In many high level languages, pointers are hidden behind objects and garbage collection. In C++, they are raw, exposed numbers representing index coordinates of your RAM. Once you realize a pointer is just an integer that stores a memory address, the fear disappears.
Let's look at how memory addresses work, how to create pointers, and how to read the memory they point to.
Memory Addresses: The Post Office Boxes of RAM
Think of your computer's RAM as a very long street with billions of houses. Each house represents one byte of memory, and each house has a unique physical address number (typically written in hexadecimal, like 0x7ffee3c8a1b0).
When we declare a variable, the operating system assigns it to one of these houses. A pointer is simply a variable whose value is the address of another house.
The Address of Operator (`&`) and Pointers
To find the memory address of a variable, we use the address of operator &. To declare a variable that can store this address, we use the pointer declaration syntax (appending * to the data type):
The Dereference Operator (`*`)
Once you have a pointer storing an address, you need a way to go inside that house and inspect or modify its contents. We do this using the dereference operator, which is also the symbol (yes, C++ uses for multiplication, pointer declarations, and dereferencing; context is everything).
Null Pointers (The Blank Note Card Hazard)
A pointer that is declared but not initialized will contain garbage memory. If you try to dereference it, your program will access random, protected RAM, triggering a crash.
To avoid this, we initialize empty pointers to nullptr (introduced in C++11). A nullptr represents address 0x0, which is guaranteed by the operating system to be unreadable and unwritable.
* The Treasure Map Metaphor: Think of a pointer as a treasure map. The coordinates on the map show you where to go. Dereferencing is traveling to those coordinates and digging up the treasure. If your pointer is set to nullptr, it is like looking at a blank map. If you try to travel to a blank coordinate to dig, you run into a brick wall at warp speed, and the operating system arrests your program. This crash is called a segmentation fault (or access violation).
Pointer Arithmetic (Stretchy Address Increments)
CPUs allow you to add integers to pointers, which is called pointer arithmetic. However, adding 1 to a pointer does not add one byte to its address value! Instead, C++ offsets the address by the size of the underlying data type.
If you have an integer pointer (pointing to a 4-byte int) and add 1, the memory address shifts forward by exactly 4 bytes! This allows you to step through array data blocks with zero latency:
Void Pointers (Generic Addresses)
Sometimes you need to store a memory address without knowing the data type of the value stored there. C++ provides void* for this purpose.
A void pointer can hold the address of any variable type, but because the compiler does not know the size of the data type, you cannot dereference a void pointer directly. You must explicitly cast it back to its specific type first: