C++ Pointers & Memory Management Cheat Sheet
Covers C++ pointer syntax, dynamic memory allocation with new and delete, pointer arithmetic, and common memory management pitfalls.
Pointer Basics
Declaring, dereferencing, and using pointers.
int x = 42;int* p = &x; // p holds the address of xstd::cout << *p; // dereference: prints 42*p = 100; // modifies x through the pointerint* nullPtr = nullptr; // preferred over NULL/0 in modern C++if (nullPtr == nullptr) { /* safe check before dereferencing */ }int arr[3] = {1, 2, 3};int* arrPtr = arr; // array decays to pointer to first element
Dynamic Memory (new/delete)
Manual heap allocation and deallocation.
int* p = new int(5); // allocate single int on the heapdelete p; // free itp = nullptr; // avoid dangling pointerint* arr = new int[10]; // allocate arraydelete[] arr; // must use delete[] for arrays, not delete// Mismatched new/delete[] is undefined behavior// Every new must be paired with exactly one delete
Pointer Arithmetic & References
Navigate arrays with pointer math, and compare with references.
int arr[5] = {10, 20, 30, 40, 50};int* p = arr;std::cout << *(p + 2); // 30, same as arr[2]p++; // advances by sizeof(int) bytesint a = 5;int& ref = a; // reference: alias, must be initialized, can't be null or reboundref = 10; // modifies a directlyvoid increment(int* p) { (*p)++; } // pass by pointervoid increment(int& r) { r++; } // pass by reference (preferred in C++)
Common Pitfalls
Memory bugs that pointers make easy to introduce.
- Dangling Pointer- Pointer that still references memory that has been freed or gone out of scope.
- Memory Leak- Heap memory allocated with new but never delete'd, unreachable and unrecoverable.
- Double Free- Calling delete twice on the same pointer; undefined behavior, often corrupts the heap.
- Wild Pointer- An uninitialized pointer holding a garbage address.
- Buffer Overflow- Writing past the bounds of allocated memory via pointer arithmetic.
- const correctness- const int* p (pointer to const data) vs int* const p (const pointer to mutable data).
unique_ptr, shared_ptr & weak_ptr in Depth
Ownership semantics, custom deleters, and breaking reference cycles.
#include <memory>// unique_ptr: exclusive ownership, move-only, zero overhead vs raw pointerstd::unique_ptr<int> up = std::make_unique<int>(42);std::unique_ptr<int> up2 = std::move(up); // up is now nullptr// up2.reset(); // explicitly free early// Custom deleter, e.g. for a C API resourceauto fileDeleter = [](FILE* f) { if (f) fclose(f); };std::unique_ptr<FILE, decltype(fileDeleter)> file(fopen("x.txt", "r"), fileDeleter);// shared_ptr: reference-counted shared ownership, atomic refcountstd::shared_ptr<int> sp1 = std::make_shared<int>(10); // one allocation for obj+control blockstd::shared_ptr<int> sp2 = sp1; // refcount now 2std::cout << sp1.use_count(); // 2// weak_ptr: non-owning observer, breaks shared_ptr reference cyclesstruct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; }; // prev avoids a cyclestd::weak_ptr<int> wp = sp1;if (auto locked = wp.lock()) { /* use *locked safely; empty if expired */ }
RAII & Exception Safety
Tie resource lifetime to object scope so cleanup happens automatically, even on exceptions.
class FileHandle { FILE* f;public: explicit FileHandle(const char* path) : f(fopen(path, "r")) { if (!f) throw std::runtime_error("open failed"); } ~FileHandle() { if (f) fclose(f); } // always runs, even during stack unwinding FileHandle(const FileHandle&) = delete; // non-copyable: single owner FileHandle& operator=(const FileHandle&) = delete; FileHandle(FileHandle&& other) noexcept : f(other.f) { other.f = nullptr; } // movable};void process() { FileHandle fh("data.txt"); // resource acquired mayThrow(); // if this throws, ~FileHandle still runs} // resource released deterministically here, no try/catch/finally needed// Rule of Five: if you write a destructor, also define/delete copy ctor,// copy assign, move ctor, and move assign - the compiler won't do it safely for you
Placement new & Alignment
Construct an object in pre-allocated storage without a separate heap allocation.
#include <new>alignas(alignof(std::string)) unsigned char buf[sizeof(std::string)];// Placement new: construct a std::string IN buf, no allocation happens herestd::string* s = new (buf) std::string("hello");s->~std::string(); // must call the destructor explicitly - no delete!// Common use: custom allocators / object pools that manage raw storagevoid* raw = ::operator new(sizeof(int) * 4); // allocate memory only, no ctor callint* arr = static_cast<int*>(raw);new (&arr[0]) int(7); // construct element 0::operator delete(raw); // must match ::operator new// std::align finds a suitably aligned sub-buffer within a larger onevoid* p = buf; std::size_t space = sizeof(buf);void* aligned = std::align(alignof(std::string), sizeof(std::string), p, space);
Ownership Transfer & Memory Order Basics
Passing unique_ptr across function boundaries and a taste of the C++ memory model.
// Transfer ownership INTO a function: take unique_ptr by valuevoid takeOwnership(std::unique_ptr<int> p) { /* p destroyed at end of scope */ }takeOwnership(std::move(up2)); // caller gives up ownership// Transfer ownership OUT of a function: return by value (guaranteed move/NRVO)std::unique_ptr<int> makeInt() { return std::make_unique<int>(5); }// Observe without owning: raw pointer or reference parametervoid observe(const int* p) { if (p) std::cout << *p; }observe(up2 ? up2.get() : nullptr); // .get() exposes the raw pointer, no ownership change// std::atomic for lock-free shared state across threads#include <atomic>std::atomic<int> counter{0};counter.fetch_add(1, std::memory_order_relaxed); // no ordering guarantee beyond atomicityint snapshot = counter.load(std::memory_order_acquire);
Advanced Memory Management Concepts
Terminology that shows up once you move past raw new/delete.
- Control Block- The heap-allocated bookkeeping struct behind shared_ptr holding the strong count, weak count, and deleter; make_shared allocates it together with the object in one block.
- Slicing- Copying a derived object into a base-by-value variable strips the derived part; store polymorphic objects via pointer/reference or smart pointer to avoid it.
- Rule of Zero- Prefer classes that own no raw resources directly (use members like unique_ptr/vector) so the compiler-generated special member functions are already correct.
- Small Buffer Optimization (SBO)- std::string/std::function store small payloads inline instead of heap-allocating, avoiding allocation overhead for common small cases.
- Dangling weak_ptr- lock() on a weak_ptr whose object was destroyed returns an empty shared_ptr rather than crashing - always check before use.
- Custom Allocator- A type satisfying the Allocator concept passed to containers (e.g. std::vector<T, MyAlloc<T>>) to control where/how memory is obtained, e.g. arena or pool allocation.
Prefer std::unique_ptr/std::shared_ptr and RAII over raw new/delete in modern C++ - raw pointers should mostly be non-owning observers, with ownership expressed through smart pointers or containers.