Memory leaks, dangling pointers & double free
Scenario
Your server application processes thousands of requests per second. It runs perfectly for 3 hours, then abruptly crashes with an 'Out of Memory' (OOM) kill signal from the operating system.
If your program logic never changed, why did it suddenly run out of memory?
Mental model
A Dangling Pointer is having a map to a house that was already demolished. A Memory Leak is losing your map to a house you own, so you can never sell it. A Double Free is hiring a demolition crew to demolish an empty lot that was already demolished yesterday.
Manual memory management in C++ forces the programmer to be a perfect accountant. Failing to balance the books results in three classic bugs: Memory Leaks (failing to free memory), Dangling Pointers (accessing memory after it was freed), and Double Free (freeing memory twice).
Explanation
When working with the Heap, the compiler does not help you clean up. You are entirely responsible for the lifecycle of the memory.
**1. Memory Leaks**
If you allocate memory using `new`, but the pointer holding the address goes out of scope before you call `delete`, that memory is lost forever. It is 'leaked'. The OS marks it as in-use, but your program has no way to access it. Over time, leaks accumulate until the OS kills your program.
**2. Dangling Pointers**
When you call `delete ptr;`, the memory on the heap is returned to the OS. However, the variable `ptr` itself still holds the old memory address. If you try to dereference `ptr` later (`*ptr = 5`), you are writing to memory you no longer own. This causes data corruption, security vulnerabilities, or segmentation faults.
**3. Double Free**
If you delete the same pointer twice, you corrupt the heap manager's internal bookkeeping. The program will almost always crash immediately.
Code examples
The Big Three Memory Bugs
void memoryBugs() {
// 1. MEMORY LEAK
int* leakPtr = new int(10);
// We reassign the pointer, losing the only reference to '10'
leakPtr = new int(20);
// 2. DANGLING POINTER
int* danglingPtr = new int(30);
delete danglingPtr; // Memory is returned to the OS
// BUG: The pointer still holds the address. Dereferencing is UB.
// *danglingPtr = 40;
// 3. DOUBLE FREE
int* dfPtr = new int(50);
delete dfPtr;
// BUG: Deleting again corrupts the heap manager.
// delete dfPtr;
}
These bugs often happen across complex codebases where ownership isn't clear. To mitigate dangling pointers, a common practice in legacy code is setting the pointer to `nullptr` immediately after deletion, as deleting a `nullptr` is a safe no-op.
Key points
- Nullify after delete: If forced to use raw pointers, set them to `nullptr` immediately after deletion to avoid dangling pointer usage.
- Use smart pointers: Modern C++ solves all three of these bugs fundamentally by using RAII and Smart Pointers (`std::unique_ptr`).
Common mistakes
- Assuming modern OS cleanup excuses leaks: While modern operating systems will reclaim all memory when a program fully exits, long-running processes (like game engines or web servers) cannot rely on this. A leak inside a server request loop will crash the server in hours.
Recall questions
- Why is it safe to call `delete` on a `nullptr`?
- How can a dangling pointer lead to a security vulnerability?
- What happens if you never call `delete` on a pointer allocated with `new`?
Questions & answers
A C++ program uses a tree structure. The developer writes a manual `cleanupTree(Node* root)` function that calls `delete` on the left and right children recursively. However, multiple nodes can share the same child in this specific graph. What bug will occur?
A Double Free (or Multiple Free). When the first parent is cleaned up, it deletes the shared child. When the second parent is cleaned up, it attempts to `delete` the exact same child pointer again, corrupting the heap and crashing.
Approach: Recognize that shared ownership with raw `delete` inevitably leads to double frees.
A developer returns a pointer to a local variable `int x = 5;`. When the caller tries to print it, it outputs garbage data. Why?
The local variable `x` was allocated on the stack and destroyed when the function returned. The returned pointer is a dangling pointer pointing to dead memory that has likely been overwritten.
Approach: Identify dangling pointers to stack memory.
Continue learning
Previous: Stack vs heap — new/delete & memory layout