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 a null pointer value. On mainstream desktop and server OSes, dereferencing address 0 is trapped and crashes the process; embedded targets may differ, so never assume null is safe to dereference.
- 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).
Virtual Memory and the OS Page Table
When you print a pointer's address (like 0x7ffee3c8a1b0), you might assume that is the exact physical location of the transistor inside your RAM stick. It is not. It is a virtual memory address.
Modern operating systems lie to every program. They give your C++ application an illusion of a massive, contiguous, private block of memory. When your pointer tries to access 0x7ffee3c8a1b0, the CPU's Memory Management Unit (MMU) intercepts the request, looks up a hardware map called the Page Table, and translates that virtual address into a physical hardware address on the fly. This prevents programs from reading each other's memory.
- The Hotel Room Metaphor: It is like checking into a hotel where your key says 'Room 1'. But when you open the door, the hotel instantly teleports you to Room 402 on the 4th floor. The OS handles the teleportation (translation), and your pointer never knows it was moved.
Array to Pointer Decay and Double Pointers
When you pass a raw C style array to a function, it loses its size information and 'decays' into a pointer to its first element. This is why C++ developers prefer std::array or std::vector over raw C arrays.
You can also create pointers that store the addresses of other pointers. These are called double pointers (), often used in legacy C APIs (like modifying a pointer inside a function):
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: