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 literals | Entire program | Read-only, very fast |
| Data | Initialized global and static variables | Entire program | Fast |
| BSS | Uninitialized/zero-init globals and statics | Entire program | Fast (no file space) |
| Stack | Local variables, function parameters, return addresses | Function scope | Fastest (no allocation) |
| Heap | Dynamic allocations (new/malloc) | Until deleted | Slower (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::vectororstaticfor data exceeding a few KB.