ReviseAlgo Logo

Pointers and Memory Management

C++ Memory Model

Stack, heap, BSS, text segments — understanding how C++ programs use memory

Interview: System programming fundamentals — storage durations, memory layout, padding and alignment are classic interview questions

C++ Memory Model

Understanding how a C++ program uses memory is essential for writing high-performance code, debugging crashes, and reasoning about resource lifetime. A running C++ program has several distinct memory regions, each with different characteristics, lifetime rules, and performance properties.

Memory Segments

Segment Contents Lifetime Speed
Text (Code)Compiled machine code, string literalsEntire programRead-only, very fast
DataInitialized global and static variablesEntire programFast
BSSUninitialized/zero-init globals and staticsEntire programFast (no file space)
StackLocal variables, function parameters, return addressesFunction scopeFastest (no allocation)
HeapDynamic allocations (new/malloc)Until deletedSlower (allocator overhead)

Stack Details

The stack grows downward on most architectures. Each function call pushes a stack frame containing: local variables, the return address, saved registers, and space for function arguments. Stack allocation is just a pointer decrement — essentially free. Stack size is limited (typically 1–8MB per thread); exceeding it causes a stack overflow. Deep recursion and large local arrays are the common culprits.

Structure Padding and Alignment

The CPU requires data to be naturally aligned — an int must reside at a 4-byte-aligned address, a double at 8-byte-aligned. The compiler inserts padding bytes between struct members to ensure alignment. Member order affects struct size: placing larger members first (or grouping by alignment) reduces padding.

Storage Duration

C++ has four storage durations: automatic (stack, scope-bounded), static (globals and static locals, entire program lifetime), thread-local (one instance per thread, thread_local), and dynamic (heap, controlled by new/delete).

Interview Corner

Q: Why is struct member ordering important for performance?

A: The compiler pads members to their natural alignment. A struct { char a; int b; char c; } has padding after a and c, totaling 12 bytes. Reordering to { int b; char a; char c; } packs to 8 bytes. Smaller structs fit more instances in a cache line — critical when arrays of structs are iterated. Use static_assert(sizeof(S) == expected) to catch layout changes.

Q: What is the static initialization order fiasco?

A: The order of initialization of static-duration objects across translation units is unspecified. If object A (in file1.cpp) depends on object B (in file2.cpp) during initialization, B might not be initialized yet. Fix: use the Meyers Singleton pattern — wrap the static in a function so it's initialized on first use: static MyType& get() { static MyType obj; return obj; }

Common Pitfalls

  • Stack overflow from large locals: Declaring large arrays on the stack (int arr[1000000]) can overflow the stack silently. Use heap (vector) for large data.
  • Returning stack-allocated memory: Returning a pointer or reference to a local variable — the stack frame is gone after the function returns, leaving a dangling reference.
  • Assuming uninitialized globals are zero: Initialized globals are zero-initialized by C++ standard (BSS segment), but local variables are not — they hold stack garbage unless explicitly initialized.

Best Practices

  • Order struct members from largest to smallest alignment to minimize padding.
  • Use static_assert(sizeof(MyStruct) == N) to detect unintended layout changes.
  • Avoid large local arrays — use std::vector or static for data exceeding a few KB.