Pointers, references, and memory
C++ lets programmers work directly with memory addresses. This enables great efficiency but demands care, and modern C++ provides tools that make memory management much safer.
Addresses and pointers
Every variable lives at a memory address. A pointer stores an address: int x = 5; int* p = &x;. & takes the address; * dereferences, so *p is 5, and *p = 9 changes x to 9. A pointer that points nowhere should be nullptr. Dereferencing a null or invalid pointer is undefined behaviour.
References
A reference is an alias for an existing variable: int& r = x; r = 10; changes x. References must be initialised and cannot be re-seated to refer to something else. Passing by reference, void addOne(int& n) { n++; }, lets a function modify the caller's variable without copying it.
Pass by value, reference, and const reference
By default, arguments are copied (pass by value), which is fine for small types. Large objects should usually be passed as const references, void print(const std::string& s), avoiding a copy while preventing modification. Pass a non-const reference only when the function is meant to change the argument.
Stack and heap
Local variables live on the stack and are destroyed automatically when they go out of scope. Memory requested with new lives on the heap until released with delete: int* arr = new int[100]; ... delete[] arr;. Forgetting delete leaks memory; deleting twice, or using memory after deleting it, is undefined behaviour.
RAII
Resource Acquisition Is Initialisation (RAII) is the central C++ idiom: tie each resource, such as memory, files, or locks, to an object whose destructor releases it. Because destructors run automatically when objects leave scope, even when exceptions are thrown, resources are always cleaned up without manual delete calls.
Smart pointers
Modern C++ uses smart pointers instead of raw new and delete. std::unique_ptr owns an object exclusively and deletes it automatically; create one with std::make_unique<Widget>(). std::shared_ptr allows shared ownership, deleting the object when the last owner goes away. Raw pointers remain useful for non-owning access.
Containers instead of raw arrays
The standard library's std::vector is a resizable array that manages its own memory; std::string manages text; std::array wraps a fixed-size array. They follow RAII, know their size, and offer bounds-checked access with at(). Preferring them over raw arrays and new eliminates most memory bugs.
Worked example
void swap(int& a, int& b) { int t = a; a = b; b = t; } swaps the caller's variables: with x = 1 and y = 2, swap(x, y) leaves x = 2 and y = 1. Written with value parameters, void swap(int a, int b), it would swap only the copies, leaving x and y unchanged: a classic beginner bug.
Key takeaways
Pointers store addresses: & takes an address and * dereferences; use nullptr for no target. References are aliases that must be initialised. Pass small types by value, large ones by const reference, and use non-const references to modify. Stack objects are freed automatically; heap memory from new needs delete, risking leaks and use-after-free. RAII ties resources to object lifetimes. unique_ptr and shared_ptr manage ownership. Prefer std::vector and std::string to raw arrays.
Flashcard
Variable storing a memory address
Check your understanding
A function void triple(int n) { n = n * 3; } is called as triple(x) with x = 4. What is x afterwards, and why?
