Memory Management Quick Reference
C++ memory management cheat sheet — smart pointers, allocators, placement new, RAII, memory model, and common pitfalls.
Ownership hierarchy
cpp
No ownership → T* / T& / std::span<T> / std::string_view
Sole ownership → std::unique_ptr<T>
Shared → std::shared_ptr<T> + std::weak_ptr<T> (cycle-breaking)
Stack/inline → T value / std::array<T,N> / std::inplace_vectorSmart pointer quick reference
cpp
// --- unique_ptr ---
auto p = std::make_unique<T>(args...); // preferred
auto arr = std::make_unique<T[]>(n); // array form
p.get(); // raw pointer (non-owning)
p.release(); // relinquish ownership → caller must delete
p.reset(); // delete & null
p.reset(ptr); // delete old, take new
std::move(p); // transfer ownership
// --- shared_ptr ---
auto s = std::make_shared<T>(args...); // single allocation (object + control block)
s.use_count(); // reference count
s.unique(); // use_count() == 1
s.reset(); // decrement count
// --- weak_ptr ---
std::weak_ptr<T> w = s;
w.expired(); // true if no shared_ptr remains
auto locked = w.lock(); // returns shared_ptr or nullptr
w.use_count(); // strong count (from shared_ptr owners)Dynamic allocation
cpp
// New / delete — avoid for owning code, use smart ptrs
T* p = new T(args...);
delete p;
T* arr = new T[n];
delete[] arr; // MUST use delete[] for arrays
// Placement new — construct in pre-allocated memory
alignas(T) char buf[sizeof(T)];
T* p = new (buf) T(args...); // no allocation — construct in buf
p->~T(); // must call destructor explicitly
// Operator new — raw allocation only, no construction
void* mem = ::operator new(sizeof(T));
T* p = new (mem) T(args...);
p->~T();
::operator delete(mem);Stack vs heap
cpp
// Stack — fast, automatic lifetime, limited size (~1-8 MB typically)
T obj(args...); // destroyed at scope exit
T arr[N]; // N must be compile-time constant
// Heap — dynamic size, manual or RAII lifetime
auto p = std::make_unique<T>(args...); // freed when p goes out of scope
auto v = std::vector<T>(n); // resizable heap buffer
// Stack-allocated containers (no heap)
std::array<T, N> a; // fixed-size, stack
std::inplace_vector<T, N> iv; // C++26 — dynamic size, stack storageRAII pattern
cpp
// Resources acquired in constructor, released in destructor
class FileHandle {
FILE* f_;
public:
explicit FileHandle(const char* path) : f_(fopen(path, "r")) {
if (!f_) throw std::runtime_error("cannot open");
}
~FileHandle() { if (f_) fclose(f_); }
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FILE* get() const { return f_; }
};
// f_ is always closed — even on exceptionMemory order (atomics)
cpp
std::atomic<int> x;
// Weakest → strongest:
x.load(std::memory_order_relaxed); // no ordering — only atomicity
x.load(std::memory_order_acquire); // "read barrier" — sees all stores before release
x.store(std::memory_order_release); // "write barrier" — visible to acquire loads
x.load(std::memory_order_seq_cst); // total order — safe default, most expensive
// Common patterns:
// - Relaxed: counters, flags you only test at sync points
// - Acquire/Release: producer-consumer (producer uses release, consumer uses acquire)
// - SeqCst: when you need global ordering across 3+ threadsAlignment
cpp
alignas(64) float cache_line[16]; // aligned to 64 bytes (cache line)
std::align_val_t{64}; // pass alignment to new
// Check alignment
alignof(T); // required alignment of T
reinterpret_cast<uintptr_t>(ptr) % alignof(T) == 0; // is ptr aligned?
// Aligned allocation (C++17)
void* p = ::operator new(size, std::align_val_t{64});
::operator delete(p, std::align_val_t{64});
// Aligned unique_ptr
auto p = std::make_unique_for_overwrite<alignas(64) float[]>(n);
// Or: custom allocatorCommon pitfalls
cpp
// Double delete
T* p = new T;
delete p;
delete p; // UB — p points to freed memory
// Fix: use smart pointers, or set p = nullptr after delete
// Use after free
T* p = new T;
delete p;
p->method(); // UB
// Dangling reference
T& get_ref() {
T local;
return local; // UB — local destroyed at return
}
// Array-pointer decay
T arr[5];
T* p = arr; // OK — pointer to first element
delete p; // UB — must use delete[], and should be new[]
// Mismatched new/delete
T* p = new T[10];
delete p; // UB — must be delete[]
// Stack overflow
void foo() {
T big[1'000'000]; // ~4 MB on stack — may overflow
// Fix: use std::vector<T>(1'000'000);
}Memory layout
cpp
// Padding — compiler adds bytes to satisfy alignment
struct Padded {
char a; // 1 byte
// 3 bytes padding
int b; // 4 bytes
char c; // 1 byte
// 3 bytes padding
};
// sizeof(Padded) = 12, not 6
// Reorder to minimize padding
struct Packed {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte
// 2 bytes padding
};
// sizeof(Packed) = 8
// Force no padding (careful with alignment requirements)
#pragma pack(push, 1)
struct Wire { uint8_t type; uint16_t length; uint32_t crc; };
#pragma pack(pop)Custom allocator (C++17 PMR)
cpp
#include <memory_resource>
// Stack buffer — no heap allocation for small vectors
std::byte buf[4096];
std::pmr::monotonic_buffer_resource arena{buf, sizeof(buf)};
std::pmr::vector<int> v{&arena};
v.reserve(100); // uses buf, not heap
// Pool allocator — fast fixed-size allocations
std::pmr::synchronized_pool_resource pool;
std::pmr::vector<std::pmr::string> strings{&pool};