4.1 Pointers
A pointer is a variable that stores a memory address instead of an ordinary value — specifically, the address of another variable.
The & operator retrieves an address ("address-of"), and the * operator, applied to a pointer, follows that address to reach the value stored there ("dereference").
Pointers are the foundation of dynamic memory allocation, linked data structures such as lists and trees, and passing large data around efficiently without copying it.
int x = 10;
int* p = &x; // p holds the memory address of x
cout << *p; // 10 -> dereferencing p gives x's value
*p = 20; // changes x through the pointer
cout << x; // 20
4.2 References
A reference is an alias — simply another name for a variable that already exists.
Unlike a pointer:
- A reference can never be null (it must be bound to a valid object upon initialization).
- Once created, it can never be reseated to refer to something else.
References are most commonly used as function parameters, letting a function modify the caller's original variable directly without the overhead of copying large objects.
void doubleIt(int& n) { // n is a reference to the caller's variable
n = n * 2;
}
int val = 5;
doubleIt(val);
cout << val; // 10 (val was modified in place!)
4.3 Stack vs Heap
C++ programs use two very different regions of memory:
- The Stack: Stores local variables and function call frames — it is fast, automatically managed by the compiler, and memory is freed the instant a function returns. The stack grows downwards in memory.
- The Heap: A much larger pool of memory that you control manually with
newanddelete(or smart pointers) — objects placed there live until you explicitly free them, which gives you flexibility but is also the single most common source of memory leaks. The heap grows upwards in memory.
4.4 Dynamic Memory (new / delete)
Sometimes a program doesn't know how much memory it will need until it is actually running — for example, when an array's size depends on runtime user input.
The new keyword allocates memory on the heap at runtime and returns a pointer to it. That memory stays reserved until you explicitly call delete on it; forgetting to do so causes a memory leak, since the program has lost its only reference to memory it never released.
int* p = new int(42); // allocate a single int on the heap
cout << *p; // 42
delete p; // free single object memory
int* arr = new int[10]; // allocate an array of 10 integers
delete[] arr; // free an array (note the square brackets [])
Mismatching new/delete with new[]/delete[] causes undefined behavior and heap corruption. Always use delete[] for anything allocated with new[].
4.5 Smart Pointers
Manually pairing every new with a matching delete is notoriously error-prone: miss one and memory leaks; delete twice (double-free) and the program crashes with a segmentation fault.
Smart pointers from <memory> wrap a raw pointer and automatically delete the memory once it is no longer needed (RAII principle):
unique_ptr<T>: Allows exactly one owner at a time. Ownership cannot be copied, only moved.shared_ptr<T>: Allows multiple owners through reference counting, freeing the memory only once the very last owner goes out of scope.weak_ptr<T>: Observes ashared_ptrwithout incrementing its reference count, which helps break cyclic references that would otherwise leak memory forever.
#include <memory>
using namespace std;
unique_ptr<int> p1 = make_unique<int>(10);
shared_ptr<int> p2 = make_shared<int>(20);
// No delete needed — freed automatically when leaving scope!