Memory
Stack vs heap, RAII, alignment, smart pointers, and placement new.
34 questions
JuniorTheoryVery commonWhat is the difference between delete and delete[]? What if you mismatch them?
What is the difference between delete and delete[]? What if you mismatch them?
delete frees one object and calls its destructor once; delete[] frees a new[] array and calls the destructor for every element. Mismatching them is undefined behaviour and may corrupt heap metadata, skip destructors, or crash.
Common mistakes
- ✗Using
deleteinstead ofdelete[]on a dynamically allocated array — the classic UB, especially for non-trivial types - ✗Not knowing that
delete nullptris always safe and does nothing — no check needed before deleting a pointer - ✗Thinking
delete[]for built-in types (int, char) is harmless mismatch — still UB because the allocator metadata differs
Follow-up questions
- →How do smart pointers (
unique_ptr<T[]>) handle the array form correctly? - →What does the compiler do when you write
new int[0]?
JuniorTheoryVery commonWhat is a memory leak? What happens if you forget to call delete?
What is a memory leak? What happens if you forget to call delete?
A memory leak is dynamically allocated memory that is never freed; if delete is omitted, the destructor never runs and the heap block stays allocated, eventually causing OOM in long-running processes.
Common mistakes
- ✗Deleting the same pointer twice (double-free) — undefined behaviour, potential heap corruption
- ✗Using
deleteinstead ofdelete[]for arrays or vice versa — undefined behaviour - ✗Relying on the OS to reclaim leaks — acceptable only for short-lived utilities, never for servers or games
Follow-up questions
- →How do you detect memory leaks in C++? Name at least two tools.
- →How does RAII prevent memory leaks compared to manual
delete?
JuniorTheoryVery commonWhat is the difference between new/delete and malloc/free?
What is the difference between new/delete and malloc/free?
new allocates storage and constructs an object; delete calls the destructor and releases storage. malloc/free only allocate and free raw bytes and know nothing about constructors, destructors, or C++ types.
Common mistakes
- ✗Mixing allocation families: malloc with delete or new with free
- ✗Using delete instead of delete[] for arrays
- ✗Forgetting that placement new must be paired with an explicit destructor call, not delete
Follow-up questions
- →Why is delete[] different from delete?
- →When would you use placement new?
JuniorTheoryVery commonWhat is a pointer? Size, operations, and pointer arithmetic.
What is a pointer? Size, operations, and pointer arithmetic.
A pointer stores the address of an object; its size is platform-dependent (8 bytes on 64-bit) regardless of the pointed-to type. Operations: *p, &x, p->m. Arithmetic is defined only inside arrays: p + n advances by n * sizeof(*p) bytes.
Common mistakes
- ✗Doing pointer arithmetic on a non-array pointer — technically UB even if it 'works'
- ✗Comparing
sizeof(int*)tosizeof(int)and assuming they are equal — false on 64-bit - ✗Using
intto store a pointer address — useuintptr_torintptr_tfrom<cstdint>
Follow-up questions
- →What is the difference between a null pointer and a dangling pointer?
- →Why is
void*special and when would you use it?
JuniorTheoryVery commonWhat is RAII and why is it the foundation of resource management in C++?
What is RAII and why is it the foundation of resource management in C++?
Resource Acquisition Is Initialization: acquire a resource in the constructor, release it in the destructor. C++ guarantees destructor calls during stack unwinding, so RAII makes cleanup automatic and exception-safe.
Common mistakes
- ✗Writing a copy constructor that copies a raw resource handle, leading to double-free
- ✗Storing RAII objects in a raw pointer and manually deleting them — bypasses RAII
- ✗Using RAII objects in C-style arrays without proper copy/move semantics
Follow-up questions
- →Write a minimal RAII wrapper for a POSIX file descriptor.
- →How does unique_ptr implement RAII? What is the overhead?
JuniorTheoryVery commonWhat is the difference between stack and heap allocation?
What is the difference between stack and heap allocation?
Stack allocation is automatic, LIFO, and bounded (typically 1–8 MB); deallocation happens when the scope exits. Heap allocation is manual or via smart pointers, effectively unbounded, but incurs allocator overhead and can fragment.
Common mistakes
- ✗Returning a pointer or reference to a stack-allocated local variable — dangling after the function returns
- ✗Storing large objects on the stack and causing a stack overflow
- ✗Manually managing heap memory with raw new/delete instead of RAII wrappers
Follow-up questions
- →What is a stack overflow? How do you detect it?
- →When is heap allocation worth the overhead?
MiddleTheoryVery commonHow do unique_ptr, shared_ptr, and weak_ptr differ?
How do unique_ptr, shared_ptr, and weak_ptr differ?
unique_ptr has exclusive ownership and is move-only. shared_ptr has shared ownership through a control block with reference counters. weak_ptr observes the same control block without extending lifetime and is used to break cycles or test whether the object is still alive.
Common mistakes
- ✗Using shared_ptr by default instead of modelling ownership
- ✗Creating two shared_ptr objects from the same raw pointer, producing two control blocks
- ✗Using weak_ptr without calling lock() before accessing the object
Follow-up questions
- →What is stored in the shared_ptr control block?
- →Why can shared_ptr cycles leak memory?
JuniorTheoryCommonWhat is the difference between calloc and malloc?
What is the difference between calloc and malloc?
malloc(size) allocates uninitialised bytes; calloc(count, size) allocates count * size bytes, zero-initialises them, and checks the multiplication for overflow. Both must be paired with free, never delete.
Common mistakes
- ✗Using
callocand assuming the object is value-initialised — zero bytes do not equal a valid C++ object for non-POD types - ✗Freeing memory allocated with
callocusingdelete— UB; must usefree - ✗Not checking the return value for null — both functions return null on allocation failure
Follow-up questions
- →What is
aligned_alloc(C++17) and when do you need it? - →How does
newdiffer frommallocbeyond calling constructors?
JuniorTheoryCommonHow does std::unique_ptr work?
How does std::unique_ptr work?
Zero-overhead RAII wrapper that exclusively owns a heap object — non-copyable but movable. On destruction or reset it calls delete (or a custom deleter). Ownership transfers explicitly via std::move(up).
Common mistakes
- ✗Trying to copy a
unique_ptr— it is non-copyable by design; usestd::movefor transfer - ✗Constructing a
unique_ptrfrom a raw pointer obtained elsewhere — creates ownership confusion; usemake_uniqueor explicit ownership transfer - ✗Storing
unique_ptrin a container and then trying to copy the container — the container also becomes non-copyable
Follow-up questions
- →What is a custom deleter for
unique_ptrand when would you need one? - →How does
unique_ptr<T[]>differ fromunique_ptr<T>in terms of the delete operation?
MiddlePerformanceCommonWhat is memory alignment and how does it affect struct layout and performance?
What is memory alignment and how does it affect struct layout and performance?
Alignment requires a type's address to be a multiple of its alignof (e.g., int at a 4-byte boundary). The compiler inserts padding so struct size != sum of members. Misaligned access faults on strict CPUs and adds bus transactions elsewhere.
Common mistakes
- ✗Assuming sizeof(struct) equals the sum of member sizes
- ✗Using reinterpret_cast between pointers of different alignment requirements
- ✗Using __attribute__((packed)) without understanding the performance cost of unaligned reads
Follow-up questions
- →How do alignas and alignof work? Give an example.
- →When would you want over-aligned storage (e.g., alignas(64) for cache lines)?
MiddleDebuggingCommonWhat happens if you write more data than you allocated (buffer overflow)?
What happens if you write more data than you allocated (buffer overflow)?
Writing past the allocated region corrupts adjacent memory: on the stack this overwrites the return address (stack smashing); on the heap it corrupts allocator metadata. Detect with AddressSanitizer, Valgrind, or stack canaries.
Open full question →Common mistakes
- ✗Using C-style
strcpy/getswithout size checks — these functions are the most common source of stack buffer overflows - ✗Allocating
nbytes but writingn+1(null terminator) — classic off-by-one in string handling - ✗Thinking
std::stringandstd::vectorprevent all overflows — incorrect bounds in manual indexing (operator[]) still overflow
Follow-up questions
- →What is ASLR and how does it make buffer overflow exploits harder but not impossible?
- →How do stack canaries detect stack buffer overflows?
MiddleDebuggingCommonWhat happens if you call free() twice on the same pointer?
What happens if you call free() twice on the same pointer?
Double-free is undefined behaviour: it corrupts the heap metadata stored next to allocations and can crash, silently corrupt data, or create a security vulnerability. AddressSanitizer (-fsanitize=address) detects it instantly at runtime.
Common mistakes
- ✗Calling
deleteon a pointer that was already deleted — same UB as double-free - ✗Not zeroing the pointer after free, then checking
if (ptr)before re-freeing — the stale non-null value passes the check - ✗Sharing a raw pointer between two containers, both of which try to free it on destruction
Follow-up questions
- →What is a use-after-free vulnerability and how does it differ from a double-free?
- →How does AddressSanitizer detect double-free without hardware support?
MiddleDebuggingCommonWhy does this leak when doWork throws?
Why does this leak when doWork throws?
If doWork throws, delete[] buf is skipped during stack unwinding, so the buffer leaks — manual new/delete is not exception-safe. Fix with RAII: std::vector<int> buf(1000); or auto buf = std::make_unique<int[]>(1000);, freed automatically while unwinding.
Common mistakes
- ✗Assuming the OS reclaims leaked heap on exception within a running process
- ✗Thinking manual new/delete is exception-safe
- ✗Catching and swallowing the exception instead of using RAII
Follow-up questions
- →What exception-safety guarantee does the RAII version give this function?
- →Why does stack unwinding run destructors but not skipped
deletestatements?
MiddleDebuggingCommonWhat's wrong with delete arr after new int[100]?
What's wrong with delete arr after new int[100]?
Undefined behavior: memory from new[] must be released with delete[], not delete. Mismatching them is UB — typically a leak or heap corruption. Fix: write delete[] arr, or avoid raw arrays via std::vector<int> or std::make_unique<int[]>(100).
Common mistakes
- ✗Assuming
deleteanddelete[]are interchangeable - ✗Thinking the mismatch only matters for types with destructors
- ✗Expecting the compiler to catch the mismatch
Follow-up questions
- →Why does
delete[]need to know the element count whendeletedoes not? - →How does
std::make_unique<int[]>(n)remove this risk entirely?
MiddleDebuggingCommonWhat happens if you access memory after delete? Does it always crash?
What happens if you access memory after delete? Does it always crash?
Access after delete is undefined behavior. It may crash, appear to work, read stale data, corrupt another object, or fail later. The allocator is allowed to reuse that block immediately, and the language gives no guarantees.
Open full question →Common mistakes
- ✗Treating a non-crashing test run as evidence that the code is safe
- ✗Setting one pointer to nullptr and forgetting that aliases still dangle
- ✗Returning references or pointers to local variables
Follow-up questions
- →How do AddressSanitizer and Valgrind detect use-after-free?
- →How does RAII reduce this class of bugs?
MiddleTheoryCommonHow does std::weak_ptr break ownership cycles and how do you use lock() safely?
How does std::weak_ptr break ownership cycles and how do you use lock() safely?
Cycles where A and B hold shared_ptr to each other never reach refcount 0 — leak. Replace one direction with weak_ptr (observes without owning); access via wp.lock() which atomically returns a shared_ptr or null.
Common mistakes
- ✗Calling
expired()and then assuming the object is still alive — race condition - ✗Forgetting that
lock()returns null for an expired pointer — must check - ✗Using
weak_ptrfor parent→child ownership whereunique_ptrsuffices
Follow-up questions
- →What's the difference between
weak_ptr::lockandweak_ptr::expired? - →How does
enable_shared_from_thiscreate the initial weak_ptr?
JuniorTheoryOccasionalWhat is realloc and when is it used?
What is realloc and when is it used?
realloc(ptr, newSize) resizes a malloc/calloc block in place if possible, otherwise allocates a new block, byte-copies, and frees the old one. On failure returns null without freeing the original. Safe in C++ only for trivially relocatable types — no ctors/dtors are called.
Common mistakes
- ✗Writing
ptr = realloc(ptr, n)— if realloc returns null, the original pointer is lost and the memory leaks - ✗Using realloc on a pointer obtained from
new— undefined behaviour - ✗Relying on realloc for non-POD objects — their move constructors and destructors are not called
Follow-up questions
- →How does
std::vectorimplement growth withoutrealloc? - →What is
std::is_trivially_relocatable(proposed) and why does it matter for allocators?
MiddleTheoryOccasionalWhat is std::allocator, and why would you write a custom allocator?
What is std::allocator, and why would you write a custom allocator?
std::allocator<T> is the default allocator containers use to obtain and release raw storage: allocate/deallocate move bytes while construction is a separate step. You write a custom allocator to pool memory, control alignment, place data in arenas or shared memory, or instrument allocation.
Common mistakes
- ✗Conflating
allocatewith object construction —allocateonly returns raw storage - ✗Writing a stateful allocator without honouring
propagate_on_container_*traits - ✗Assuming a custom allocator changes the size or layout of
Trather than its storage
Follow-up questions
- →How does
std::allocator_traitsdecouple containers from allocator details? - →What problems arise with a stateful allocator during container copy and swap?
MiddleTheoryOccasionalHow do you detect memory leaks in C++?
How do you detect memory leaks in C++?
Use Valgrind (--leak-check=full, no recompilation needed), AddressSanitizer (-fsanitize=address, ~2x slowdown, ideal for CI), or LeakSanitizer (-fsanitize=leak, leak-only and faster). On Windows: CRT debug heap or Dr. Memory.
Common mistakes
- ✗Running leak checks only manually — should be automated in CI so regressions are caught immediately
- ✗Ignoring 'still reachable' leaks in Valgrind — pointers held in globals or static locals are still reachable but never freed; they are real leaks for long-running services
- ✗Not testing with a real workload — leaks in rarely-executed paths are only visible under realistic usage
Follow-up questions
- →How does AddressSanitizer track heap allocations to detect use-after-free and out-of-bounds?
- →What is heap profiling and how is it different from leak detection?
MiddleTheoryOccasionalDoes a reference take memory and how is it implemented at the ABI level?
Does a reference take memory and how is it implemented at the ABI level?
Logically a reference is an alias; the standard does not mandate a representation. In practice references are implemented as pointers — a reference member adds one pointer of storage, a reference parameter is passed as a pointer. The compiler may elide them entirely.
Common mistakes
- ✗Trying to take the address of a reference and expecting a different value than the target's address
- ✗Assuming reference members add zero size to a class — they typically add pointer size
- ✗Confusing 'reference is alias' with 'no representation' — abstractly true, concretely a pointer
Follow-up questions
- →Why is a class with a reference member non-default-constructible?
- →Can a reference be
nullptr? Why or why not?
MiddleTheoryOccasionalWhat is a stack overflow? Causes and prevention.
What is a stack overflow? Causes and prevention.
Occurs when the call stack exceeds its limit (typically 1–8 MB); the OS detects it via a guard page and sends SIGSEGV. Common causes: deep or infinite recursion, large stack buffers. Prevention: bound recursion, convert to iteration, move large buffers to the heap.
Common mistakes
- ✗Declaring a large array on the stack in a recursive function — each call frame multiplies the allocation
- ✗Not adding a base case to a recursive function — trivial infinite recursion
- ✗Trusting compiler tail-call optimisation to handle unbounded recursion — C++ does not guarantee TCO
Follow-up questions
- →How can you increase the stack size on Linux and when should you?
- →What is a trampolined recursion and how does it avoid stack overflow?
SeniorCodeOccasionalImplement a fixed-size memory pool using placement new
Implement a fixed-size memory pool using placement new
Allocate a raw aligned buffer, use placement new to construct objects into it, call the destructor explicitly before reusing the slot, and never call delete on a placement-new pointer.
Open full question →Common mistakes
- ✗Calling delete on a pointer obtained from placement new — undefined behaviour
- ✗Not satisfying alignment requirements of the stored type
- ✗Forgetting to call the destructor explicitly before recycling a slot
Follow-up questions
- →How does std::allocator relate to placement new?
- →What is the purpose of std::launder and when do you need it?
SeniorTheoryOccasionalWhen and how to work with raw pointers and manual memory management in modern C++?
When and how to work with raw pointers and manual memory management in modern C++?
Limit raw new/delete to custom allocators, RAII wrappers, and C-API interop. Non-owning observers may stay raw — they do not extend lifetime. Rule of thumb: owning raw pointers must be wrapped in a smart pointer immediately.
Common mistakes
- ✗Storing owning raw pointers in class members without a clear destructor — leaks when the class is destroyed
- ✗Passing raw pointers returned from C APIs directly into C++ containers without converting to RAII first
- ✗Using raw pointer arithmetic instead of
std::span(C++20) for array views —spancarries size and prevents overrun
Follow-up questions
- →What is
std::spanand how does it replace pointer+size pairs? - →When would you use
std::unique_ptr<T, CustomDeleter>over a plain raw pointer in a C-API wrapper?
SeniorTheoryOccasionalWhat are std::uninitialized_copy / uninitialized_fill for and when would you write them yourself?
What are std::uninitialized_copy / uninitialized_fill for and when would you write them yourself?
They construct objects in raw memory via placement new and roll back partially-constructed elements on exceptions. You write them yourself when implementing a container — separating allocate from construct enables reserve/resize semantics.
Common mistakes
- ✗Forgetting exception safety — partial construction on throw leaks objects
- ✗Calling destructor on uninitialised slots — UB
- ✗Using
std::copyto fill raw memory — destructors of fictitious objects run on assignment
Follow-up questions
- →What does
std::allocator_traits::constructadd over direct placement new? - →When should you use
std::start_lifetime_as(C++23)?
SeniorTheoryOccasionalWhat are common C++ security vulnerabilities? How do they work?
What are common C++ security vulnerabilities? How do they work?
Common C++ vulnerabilities: buffer overflow, use-after-free, integer overflow, format string (printf(user_input)), double-free, TOCTOU races. Mitigations: RAII, smart pointers, ASan, stack canaries.
Common mistakes
- ✗Thinking smart pointers prevent all use-after-free — a raw pointer obtained from
get()on ashared_ptrcan dangle if theshared_ptris destroyed while the raw pointer is still in use - ✗Ignoring signed integer overflow — it is undefined behaviour in C++, not a guaranteed wrap; the compiler may optimise assuming no overflow, leading to unexpected code
- ✗Trusting
strncpyto null-terminate —strncpydoes NOT null-terminate if the source is >= n bytes; usestrlcpyorsnprintfinstead
Follow-up questions
- →How does ASLR (Address Space Layout Randomisation) mitigate buffer overflow exploitation?
- →What is Control Flow Integrity (CFI) and how does Clang implement it?
MiddleTheoryRareHow do you allocate memory with a specific alignment in C++?
How do you allocate memory with a specific alignment in C++?
Use alignas(N) T x; for stack/static, std::aligned_alloc(N, size) with free (C++17), or operator new(size, std::align_val_t{N}) with matching operator delete (C++17). Plain new T aligns only to alignof(T).
Common mistakes
- ✗Using
aligned_allocthendelete— must usefree - ✗Allocating with
newfor an over-aligned type compiled before C++17 — alignment not guaranteed - ✗Confusing
alignaswithalignof—alignasrequests,alignofqueries
Follow-up questions
- →Why does SIMD often require 16-, 32-, or 64-byte alignment?
- →How does
std::pmr::polymorphic_allocatorhandle alignment?
SeniorTheoryRareWhen and how do you overload operator new / operator delete?
When and how do you overload operator new / operator delete?
Overload them to pool small objects, track allocations, or enforce alignment. Define them as a matching pair, member or global; operator new takes size_t and returns void* (throwing bad_alloc or being noexcept for the nothrow form). A class member version is used by new T for that type and its derived classes.
Common mistakes
- ✗Overloading
operator newbut forgetting the matchingoperator delete, including the array forms - ✗Confusing
operator new(raw storage) with thenewexpression (storage plus construction) - ✗Ignoring that a member
operator deleteis also used when a derived object is deleted via base pointer
Follow-up questions
- →How does the array form
operator new[]differ, and why is its size argument tricky? - →Why must a class-specific
operator deletebestaticand ideally take asize_t?
SeniorTheoryRareWhat does std::pmr::polymorphic_allocator solve over template-parameter allocators?
What does std::pmr::polymorphic_allocator solve over template-parameter allocators?
Classic allocators are template parameters, so vector<int,A> and vector<int,B> are different types that cannot interoperate. polymorphic_allocator holds a runtime memory_resource* pointer, so allocation strategy is chosen at runtime without changing the container type or causing template bloat.
Common mistakes
- ✗Expecting compile-time dispatch —
memory_resourcecalls are virtual at runtime - ✗Believing
pmr::vectoris the same type asstd::vector— it isvector<T, pmr::polymorphic_allocator<T>> - ✗Letting a
monotonic_buffer_resourceoutlive its backing buffer or be used past its capacity
Follow-up questions
- →When would you pick
monotonic_buffer_resourceoverunsynchronized_pool_resource? - →Why does
polymorphic_allocatornot propagate on container copy or move assignment?
SeniorTheoryRareHow is realloc used in container implementations? Pitfalls.
How is realloc used in container implementations? Pitfalls.
C++ containers do NOT use realloc because it only byte-copies and cannot call move constructors or fix self-referential pointers. std::vector allocates a new block, move-constructs each element (or copies if move is not noexcept), then destroys originals.
Common mistakes
- ✗Using
reallocin a custom C++ container for non-trivial types — move constructors and destructors are bypassed - ✗Not accounting for self-referential types (e.g., a type that stores a pointer to its own member) — byte-copy breaks the invariant
- ✗Confusing
std::vector::reserve(just allocates) with reallocation triggered bypush_back(allocates and moves)
Follow-up questions
- →What is the SBO (small buffer optimisation) in
std::stringand why does it conflict withrealloc? - →How might a future P1144
trivially_relocatableproposal allow containers to usereallocsafely?