Lesson 15 • Advanced
Modern C++ Memory Model
Understand how C++ organises memory into segments, how smart pointers work internally, and how object lifetimes are managed.
What You'll Learn
- ✓ Memory layout: text, data, stack, heap
- ✓ How smart pointers are implemented internally
- ✓ Object lifetime rules (stack, heap, static)
- ✓ Control blocks and reference counting
Memory Layout of a C++ Program
When your C++ program runs, the OS divides its address space into distinct segments:
| Segment | Contains | Grows |
|---|---|---|
| Text | Compiled machine code | Fixed |
| Data | Global & static variables | Fixed |
| BSS | Uninitialised globals (zeroed) | Fixed |
| Heap | Dynamic allocations (new) | ↑ Upward |
| Stack | Local variables, function calls | ↓ Downward |
The stack and heap grow toward each other. A stack overflow occurs when the stack grows past its limit (usually 1-8 MB). Deep recursion is the most common cause.
Memory Layout
Inspect addresses to see where different variables live in memory
#include <iostream>
using namespace std;
// Global variable — stored in data segment
int globalVar = 42;
// Static variable — also data segment
static int staticVar = 100;
void demonstrateLayout() {
// Local variable — stack
int stackVar = 10;
// Dynamic allocation — heap
int* heapVar = new int(99);
cout << "=== Memory Layout ===" << endl;
cout << "Global (data): " << &globalVar << " = " << globalVar << endl;
cout << "Static (data): " << &staticVar <<
...Smart Pointer Internals
unique_ptr is essentially a raw pointer with a destructor — zero overhead at runtime. It's the same size as a raw pointer. shared_ptr is larger because it stores two pointers: one to the object and one to the control block.
The control block contains the strong reference count, weak reference count, deleter, and allocator. make_shared is more efficient because it allocates the object and control block in a single memory allocation.
Smart Pointer Internals
Explore sizes, reference counts, and custom deleters
#include <iostream>
#include <memory>
using namespace std;
class Widget {
int id;
public:
Widget(int i) : id(i) { cout << "Widget " << id << " born" << endl; }
~Widget() { cout << "Widget " << id << " died" << endl; }
int getId() const { return id; }
};
int main() {
cout << "=== Smart Pointer Internals ===" << endl;
// unique_ptr: just a pointer + deleter — same size as raw pointer
unique_ptr<Widget> u1 = make_unique<Widget>(1);
cout << "sizeof(unique_ptr):
...Object Lifetime
Watch constructors and destructors fire in different lifetime scenarios
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Logger {
string tag;
public:
Logger(string t) : tag(t) {
cout << "[" << tag << "] Constructor called" << endl;
}
Logger(const Logger& other) : tag(other.tag + "_copy") {
cout << "[" << tag << "] Copy constructor" << endl;
}
Logger(Logger&& other) noexcept : tag(move(other.tag) + "_moved") {
cout << "[" << tag << "] Move constructor" << endl;
}
~Logger() {
...Common Mistakes
⚠️ Stack overflow from recursion: Each function call uses stack space. Infinite or deep recursion will exhaust the stack.
⚠️ Storing addresses of temporaries: A temporary object is destroyed at the end of its statement. Pointers to it become dangling immediately.
⚠️ Assuming static order: Static objects in different translation units have no guaranteed construction order. This is the "static initialization order fiasco."
Pro Tips
💡 Use make_shared over new: make_shared<T>() does one allocation instead of two, improving cache locality and performance.
💡 emplace_back over push_back: emplace_back constructs objects in-place inside the container, avoiding extra moves.
💡 Reserve vector capacity: Call vec.reserve(n) before inserting to avoid repeated reallocations and moves.
📋 Quick Reference
| Concept | Detail |
|---|---|
| Stack limit | ~1-8 MB (OS-dependent) |
| sizeof unique_ptr | Same as raw pointer (8 bytes on 64-bit) |
| sizeof shared_ptr | 2 × pointer size (16 bytes on 64-bit) |
| Destruction order | LIFO for stack, manual for heap |
| Static lifetime | Constructed before main, destroyed after |
Lesson Complete!
You now understand how C++ organises memory, how smart pointers work under the hood, and how object lifetimes are managed. Next up: Advanced OOP — virtual tables, polymorphic dispatch, and multi-level inheritance.
Sign up for free to track which lessons you've completed and get learning reminders.