Mid-level 5 min · March 06, 2026

C++ Memory Leaks — 1.5 GB After 50 Tab Cycles

RSS grew to 1.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Memory leak: allocated heap memory that is never freed
  • Detection tools: Valgrind (memcheck), AddressSanitizer (ASan)
  • Prevention: RAII, smart pointers (unique_ptr, shared_ptr, weak_ptr)
  • Performance impact: RSS growth ~1-10 MB/hour typical, OOM kill at 3-5 GB
  • Production gotcha: leak detection tools add 2-10x slowdown, disable in production
  • Biggest mistake: relying on manual new/delete instead of RAII wrappers
Plain-English First

Imagine you're renting storage units. Every time you need to store something, you rent a new unit. A memory leak is when you forget to return the key after you've emptied the unit — the unit stays rented, the bill keeps growing, and eventually you run out of storage units entirely. In C++, your program rents memory from the heap using 'new' and must return it using 'delete'. When it forgets to return that memory, it leaks — silently, invisibly — until your server crashes at 3am.

Memory leaks are the silent killers of C++ applications. Unlike a crash that screams at you immediately, a memory leak is a slow bleed — your process's RSS (Resident Set Size) climbs steadily over hours or days, swap starts thrashing, and eventually the OOM killer on Linux terminates your process with no warning. This is exactly what happened in a famous 2015 Firefox bug where tab-related allocations were never freed, causing hundreds of megabytes of leak per browsing session. The C++ runtime has no garbage collector watching your back. Every byte you allocate is your responsibility to free.

The core problem is the gap between WHEN you allocate memory and WHEN you need to free it. Throw exceptions, early returns, complex ownership semantics, or circular references into the mix, and that gap becomes a minefield. Raw pointers in C++ carry no metadata about who owns them, who should free them, or whether they're still valid. The language gives you a chainsaw — powerful, but it doesn't stop you from cutting your own hand off.

By the end of this article, you'll understand exactly how the heap allocator works under the hood, why leaks happen even in 'careful' code, how to use Valgrind and AddressSanitizer to pinpoint leaks down to the exact line, and how to architect your code with RAII and smart pointers so that leaks become structurally impossible. You'll also leave with the exact vocabulary and depth to ace memory management questions in senior C++ interviews.

What Is a Memory Leak and Why Does It Happen?

A memory leak occurs when a program allocates memory from the heap (using new, malloc, or calloc) and never deallocates it. The leaked memory remains allocated until the process exits. The operating system reclaims it on process termination, but during the process lifetime, the leak gradually reduces available heap space.

Leaks happen because C++ does not have automatic garbage collection. The programmer must explicitly pair every allocation with a deallocation. Real-world causes include:

  • Exception thrown before delete is reached.
  • Early return paths that skip cleanup.
  • Overwriting the only pointer to allocated memory (pointer reassignment without delete).
  • Circular references involving shared_ptr.
  • Missing virtual destructors, causing derived resource not to be released.
leak_example.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// TheCodeForge — C++ memory leak example
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

void leak_function() {
    Resource* r = new Resource();  // allocated
    // some condition that causes early return
    if (rand() % 2 == 0) {
        // missing delete r; -> memory leak
        return;
    }
    delete r;
    return;
}

int main() {
    for (int i = 0; i < 10; ++i) {
        leak_function();
    }
    return 0;
}
Output
Resource acquired (x10) but no "Resource released" for half the calls -> memory leak
Mental Model: The Bad Bookkeeper
  • Every allocation grabs a contiguous block from the free list.
  • Every deallocation returns the block to the free list (coalescing neighbors).
  • A leak means the block stays in use but is no longer reachable — the free list shrinks permanently.
  • Fragmentation makes the problem worse: small leaks fragment the heap, causing future allocations to fail even if total free memory is large.
Production Insight
A single leaked byte sounds harmless, but production servers run for weeks. A leak of 100 bytes per request at 1000 RPS = ~8.6 MB/day. After 30 days, that's 258 MB of unreachable memory.
Real story: a trading system leaked 64 bytes per order. After 10 million orders, the process hit 640 MB and was OOM-killed during peak hours.
Rule: small leaks are dangerous because they compound. Profile early, profile often.
Key Takeaway
A memory leak is allocated memory with no remaining pointer to it — the heap allocator cannot reclaim it.
Every new must be paired with a delete or ownership transferred to a smart pointer.
Leaks are deterministic: find the missing deallocation, fix the control flow.

Heap Allocator Internals: How Leaks Steal Memory

The heap allocator (glibc's malloc, tcmalloc, jemalloc) manages a pool of memory blocks. When you call new, it finds a free block via the free list, marks it as used, and returns the address. When you call delete, it returns the block to the free list. Coalescing merges adjacent free blocks into larger ones.

A leak does not just lose memory — it also fragments the heap. If many small blocks are leaked in the middle of the heap, the free list contains many small holes. A request for a larger contiguous block may fail even though total free memory is sufficient. This is called external fragmentation.

Production allocators also maintain thread caches to reduce lock contention. Leaked memory in thread caches is even harder to track because it may appear as "in use" but not actually reachable by any pointer. Tools like Valgrind intercept every malloc/free call to track reachable pointers.

fragmentation_sim.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// TheCodeForge — heap fragmentation simulation
#include <iostream>
#include <vector>

int main() {
    // allocate large blocks, free every other one -> fragmentation
    std::vector<int*> blocks;
    for (int i = 0; i < 100; ++i) {
        blocks.push_back(new int[1024]);  // 4KB each
    }
    // free even indices
    for (size_t i = 0; i < blocks.size(); i += 2) {
        delete[] blocks[i];
    }
    // now heap has 50 holes of 4KB each, interspersed with used blocks
    // a new request for a 200KB contiguous block may fail!
    int* big = new int[50*1024];  // 200KB
    if (!big) std::cout << "Allocation failed due to fragmentation\n";
    else std::cout << "Allocation succeeded (if not fragmented)\n";
    delete[] big;
    for (size_t i = 1; i < blocks.size(); i += 2) {
        delete[] blocks[i];
    }
    return 0;
}
Output
May output "Allocation failed due to fragmentation" on a real system after many runs
Fragmentation Is a Silent Performance Killer
Even without leaks, fragmentation can cause OOM. Use memory pools or custom allocators for fixed-size allocations. Consider jemalloc's --enable-prof to track fragmentation.
Production Insight
Thread caches in tcmalloc can hold up to 256 KB per thread of freed memory not returned to the central heap. If threads die without flushing their caches, that memory is effectively leaked.
Diagnose with 'mallctl(MIB, oldp, &oldlenp, newp, newlenp)' in jemalloc to query thread cache sizes.
Rule: Use a memory profiler (heaptrack, massif) to see where allocations happen, not just where they leak.
Key Takeaway
Heap fragmentation from small leaks can kill allocation success before RSS reaches OOM limits.
Leaks fragment the heap; fragmentation causes larger allocations to fail spuriously.
Use custom allocators and memory profiling to detect both leaks and fragmentation.

RAII and Smart Pointers: Making Leaks Impossible

RAII (Resource Acquisition Is Initialization) ties resource lifetime to object lifetime. When an object goes out of scope, its destructor automatically frees the resource. This makes leaks structurally impossible if the resource is owned by an RAII wrapper.

Smart pointers are RAII wrappers for heap memory
  • std::unique_ptr: exclusive ownership, no copy, move-only. Leak-free because the destructor calls delete.
  • std::shared_ptr: shared ownership via reference counting. Leak only if circular references exist.
  • std::weak_ptr: non-owning observer that breaks reference cycles.

Rule: Never use raw new/delete outside of a RAII wrapper or low-level allocator. The only exceptions are when writing custom containers or interfacing with C libraries.

raii_example.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TheCodeForge — RAII with unique_ptr
#include <memory>
#include <iostream>

struct Widget {
    ~Widget() { std::cout << "Widget destroyed\n"; }
    void doWork() const { std::cout << "Work\n"; }
};

void process() {
    auto w = std::make_unique<Widget>();  // RAII
    w->doWork();
    // no delete needed! destructor called automatically
}

int main() {
    process();  // Output: "Work" then "Widget destroyed"
    // No leak even if exception thrown inside process()
    return 0;
}
Output
Work
Widget destroyed
Mental Model: The Hotel Bellhop
  • Destructors are called for stack unwinding on exceptions, returns, and normal scope exit.
  • No manual cleanup needed — reducing cognitive load.
  • unique_ptr is zero-overhead over raw pointer in release builds.
  • shared_ptr has reference counting overhead (atomic increments) but often acceptable.
  • weak_ptr breaks cycles: use when two objects reference each other.
Production Insight
A common RAII pitfall is using shared_ptr in a graph-like structure where nodes hold pointers to each other. This creates a reference cycle. Neither node's reference count drops to zero and both leak.
Fix: Use weak_ptr for one direction of the cycle.
Real example: a plugin system where PluginManager held shared_ptr to Plugin, and Plugin held shared_ptr back to PluginManager. Thousands of plugin load/unload cycles leaked hundreds of MB.
Rule: In any bidirectional ownership graph, at least one direction must be a weak_ptr.
Key Takeaway
RAII makes leaks impossible by tying resource lifetime to object lifetime.
Prefer unique_ptr for exclusive ownership, shared_ptr for shared ownership, weak_ptr for cycles.
Smart pointers are not zero-cost — measure, but rarely the bottleneck.

Using Valgrind and AddressSanitizer to Find Leaks

Valgrind (memcheck): Runs the program under a synthetic CPU. Intercepts all malloc/free/new/delete calls. Tracks reachability by scanning memory for pointer values. Reports: - definitely lost: pointer to block overwritten, no way to free. - indirectly lost: block is only referenced by other lost blocks. - possibly lost: pointer to interior of block exists, but start address might be lost. - still reachable: pointer still exists but block not freed before exit (technically not a leak, but often suggests design issue).

AddressSanitizer (ASan): Compile-time instrumentation. For every allocation, it records a shadow byte that tracks access permissions. At runtime, it detects heap-use-after-free, buffer overflow, and memory leaks (with detect_leaks=1). Much faster than Valgrind (2-4x slowdown vs 10-50x).

Both tools should be used during development and in stress testing. They are too slow for production. Use core dumps and heap profiling in production.

run_tools.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Compile with debug symbols (ASan)
g++ -g -O0 -fsanitize=address -fno-omit-frame-pointer leak_example.cpp -o leak_asan

# Run with ASan leak detection
ASAN_OPTIONS=detect_leaks=1:abort_on_error=1 ./leak_asan

# Compile for Valgrind (no sanitizer)
g++ -g -O0 leak_example.cpp -o leak_valgrind

# Run under Valgrind
valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./leak_valgrind

# Example output from ASan:
# =================================================================
# ==12345==ERROR: LeakSanitizer: detected memory leaks
# Direct leak of 40 byte(s) in 5 object(s) allocated from:
#     #0 0x7f8c1c2b1b40 in operator new(unsigned long)
#     #1 0x400a1f in leak_function() /tmp/leak_example.cpp:14
#     #2 0x400a76 in main /tmp/leak_example.cpp:22
# SUMMARY: AddressSanitizer: 40 byte(s) leaked in 5 allocation(s).
Output
ASan reports exact allocation stacks for each leaked block.
Tool Selection Guide
Use Valgrind for detailed leak tracing and when you cannot recompile. Use ASan for daily development — it catches more leaks faster. For production, enable mallctl (jemalloc) or tcmalloc's heap profiler.
Production Insight
ASan in production is not feasible — overhead is too high. But you can run it on a canary instance with a copy of production traffic for 10-15 minutes. That often catches leaks triggered only under real load patterns.
Real story: A gaming server leaked memory only when 100+ players joined simultaneously. Unit tests never hit that. A canary with ASan caught the leak in 5 minutes of real load.
Rule: Run leak detection on synthetic production traffic (recorded replay) before each release.
Key Takeaway
Valgrind: accurate but slow (10-50x slowdown).
ASan: fast enough for development, must be enabled at compile time.
Production: use core dumps and heap profilers (gperftools, jemalloc prof).
Always compile with debug symbols and run leak checks on canary traffic.

Production Debugging Without Tools: Core Dumps and Heap Analysis

When a process is OOM-killed, you lose the process. But if you enable core dumps (ulimit -c unlimited), the OS saves the entire memory image to a file. You can analyze it post-mortem: - Use gdb to inspect pointers and see what blocks are reachable. - Use pstack to get thread stacks. - Use malloc_info(0, stderr) to dump glibc's internal allocation statistics.

Heap profiling tools like gperftools (Google Performance Tools) or jemalloc's built-in profiler can be enabled at runtime with minimal overhead (~5%). They generate heap snapshots that you can diff over time to find growing allocations.

In production, never run Valgrind or ASan. Instead, enable lightweight heap profiling continuously. Set a threshold: if RSS grows more than 10% in an hour, alert the pager with a heap snapshot attached.

production_heap_profile.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Enable core dumps
echo '/tmp/core.%p' | sudo tee /proc/sys/kernel/core_pattern
ulimit -c unlimited

# Run with gperftools heap profiler
env HEAPPROFILE=/tmp/myapp.hprof HEAP_PROFILE_ALLOCATION_INTERVAL=2097152 ./myapp

# Signal to dump current heap profile
kill -USR2 $(pgrep myapp)

# Analyze diff between two snapshots
pprof --text --base=/tmp/myapp.hprof.0001.heap /tmp/myapp.hprof.0002.heap

# Example pprof output:
# Total: 256.0 MB
#  85.0 MB (33.2%): 0x7f8c1c2b1b40 /usr/lib/libstdc++.so.6.0.30
#  40.0 MB (15.6%): my_func /home/user/myapp.cpp:150
Output
Heap snapshot diff shows which functions are responsible for growth.
Core Dumps Can Be Huge
A process using 8 GB RSS will generate an 8 GB core dump. Ensure disk space is available. Consider using coredumpctl (systemd) to compress and manage cores. Alternatively, limit by ulimit -c 2097152 (2 GB max).
Production Insight
A common mistake is to rely solely on Valgrind during development and skip production heap profiling. Leaks that emerge under high concurrency, specific input patterns, or timing issues often escape unit tests.
Real story: a web server leaked memory only when HTTP keep-alive connections timed out under load. The timeout handler reallocated the connection buffer every time, but never freed the previous one. Valgrind tests never caught it because the timeout never triggered in single-threaded tests.
Rule: Deploy heap profiling to 1-5% of production instances always. Diff snapshots every hour.
Key Takeaway
Core dumps allow post-mortem heap analysis — but only if the OOM killer doesn't truncate them.
Heap profilers (gperftools, jemalloc) add <5% overhead and can run continuously in production.
Diff heap snapshots to identify growing allocations over time.

Common Leak Patterns and How to Fix Them

  1. Missing virtual destructor: Base class pointer to derived object. If base destructor is not virtual, derived destructor never runs, leaking derived members.
  2. Circular shared_ptr references: A->B and B->A both hold shared_ptr. Neither reference count reaches zero. Fix: use weak_ptr in one direction.
  3. Exception in constructor: If constructor throws after new but before assigning to a smart pointer, the raw allocation is leaked. Fix: use RAII wrappers for all allocations in constructor initializer lists.
  4. Unhandled allocation failure: new may throw std::bad_alloc. If not caught, the code after it may never execute, potentially leaking prior allocations. Fix: always handle exceptions or use nothrow new.
  5. Forgetting to delete in setter/reassignment: ptr = new X; overwrites old pointer without delete. Fix: always delete before reassigning, or use smart pointers.
  6. Thread-local storage leaks: If a thread exits without cleaning up its TLS blocks, those blocks remain allocated until process death. Fix: register cleanup functions with pthread_key_create destructor.
virtual_destructor_leak.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TheCodeForge — missing virtual destructor leak
#include <iostream>

class Base {
public:
    ~Base() { std::cout << "Base destructor\n"; }  // not virtual!
};

class Derived : public Base {
    int* data;
public:
    Derived() : data(new int[100]) {}
    ~Derived() { delete[] data; std::cout << "Derived destructor\n"; }
};

int main() {
    Base* b = new Derived();
    delete b;  // calls ~Base() only, not ~Derived()! -> leak of data array
    return 0;
}
Output
Base destructor
// Derived destructor never called -> 400 bytes leaked
Always Make Base Destructors Virtual
This is the #1 C++ gotcha in interviews and production. If you have any derived class, make the base destructor virtual. This costs a vtable entry, but prevents entire subtrees of resources from leaking.
Production Insight
A missing virtual destructor in a widely-used library can cause leaks across all dependent services. Example: a in-house logging library had a non-virtual destructor. When used polymorphically, every log line leaked a small buffer. Over a billion log lines, the leak was 10+ GB per day.
Rule: enforce virtual destructors via static analysis tools (clang-tidy modernize-use-override).
Key Takeaway
Six patterns cause 90% of C++ memory leaks.
Virtual destructors, circular shared_ptr, exception safety, reassignment, TLS, and missing cleanup.
Use RAII and static analysis to prevent all six.

Static Analysis and Coding Standards: Prevent Leaks at Compile Time

The best tool for memory leaks is prevention. C++ has evolved to make leaks harder to write: - -Wall -Wextra -Werror flags catch some cases. - clang-tidy with cppcoreguidelines-no-malloc, modernize-use-auto, modernize-use-equals-default. - cppcheck for static analysis: detects missing delete, mismatched allocation/deallocation, leaky containers. - -fsanitize=address as part of CI pipeline.

Coding standards
  • Never use new/delete directly (exception: custom allocators).
  • Use std::make_unique and std::make_shared.
  • Use RAII wrapper for every resource (file handle, socket, mutex).
  • Prefer containers over raw arrays.
  • Use C++17's std::shared_ptr with std::make_shared for exception safety.
  • Mark classes with final if not intended for inheritance to avoid virtual destructor issues.
clang_tidy_example.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// TheCodeForge — clang-tidy will flag this
#include <memory>

class MyClass {
public:
    MyClass() {
        ptr = new int[100];  // clang-tidy: warning: use make_unique
    }
    ~MyClass() {
        delete[] ptr;
    }
private:
    int* ptr;
};

// Better:
class MyClassFixed {
public:
    MyClassFixed() : ptr(std::make_unique<int[]>(100)) {}
private:
    std::unique_ptr<int[]> ptr;
};
Output
clang-tidy outputs: warning: use 'std::make_unique' instead of new/delete
CI Pipeline Integration
Add clang-tidy and cppcheck to your code review CI. Reject PRs that introduce any new new or delete outside of low-level modules.
Production Insight
A team of 20 engineers was shipping an application that leaked ~5 MB per hour. After adopting clang-tidy with the cppcoreguidelines checks and banning raw new/delete in code reviews, the leak rate dropped to zero within one release cycle.
Rule: Automate prevention. Code review alone catches less than 30% of leaks. Static analysis catches 80%+.
Key Takeaway
Static analysis prevents leaks at compile time — the cheapest fix possible.
Ban raw new/delete in code review. Use clang-tidy and cppcheck in CI.
C++17 and later provide safe primitives (make_unique, make_shared) — use them.
● Production incidentPOST-MORTEMseverity: high

The Firefox Tab Leak That Ate 400 MB Per Session

Symptom
Firefox process RSS grew linearly with number of tabs opened and closed. After 50 tab open/close cycles, RSS exceeded 1.5 GB and the browser became unresponsive.
Assumption
The team assumed closing a tab would trigger DOM cleanup and free associated JavaScript objects. They thought the garbage collector would handle it.
Root cause
Circular references between DOM nodes and JavaScript event listeners prevented garbage collection. The listener held a reference to the node, and the node held a reference to the listener via internal data structures. The GC could not collect either.
Fix
Used WeakReference patterns for event listeners and added explicit cleanup in the tab close handler. Also implemented a memory pressure callback that proactively released cached DOM data.
Key lesson
  • Never assume GC handles circular references across C++/JavaScript boundaries.
  • Always add explicit destructors or cleanup handlers for long-lived objects.
  • Profile RSS growth over hours, not minutes — small leaks compound to megabytes.
  • Use heap profiling tools early in development; retrofitting is expensive.
Production debug guideQuick diagnostic map for production incidents involving memory growth4 entries
Symptom · 01
RSS grows steadily over hours, no spike
Fix
Run Valgrind with --leak-check=full on a test workload. Focus on 'definitely lost' blocks.
Symptom · 02
RSS grows quickly (MB/sec) after specific operation
Fix
Use AddressSanitizer (ASan) with detect_leaks=1. Compile with -fsanitize=address -fno-omit-frame-pointer.
Symptom · 03
OOM kill in production, no reproduction locally
Fix
Enable core dumps (ulimit -c unlimited). Analyze heap with gdb or jemalloc heap profiling. Check /proc/<pid>/smaps for heap growth.
Symptom · 04
Swap usage grows, app slows, then OOM
Fix
Check /proc/meminfo for Committed_AS. Use 'top' or 'htop' to find leaking process. Attach GDB and call malloc_info(0, stderr) to dump allocation stats (glibc).
★ Memory Leak Debugging Cheat SheetThree common leak patterns and the exact commands to diagnose them in production.
Suspected leak, need to confirm
Immediate action
Check /proc/<pid>/status VmRSS every 5 seconds: watch -n 5 'cat /proc/$(pgrep myapp)/status | grep VmRSS'
Commands
valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./myapp
ASAN_OPTIONS=detect_leaks=1:abort_on_error=1 ./myapp
Fix now
Wrap all new/delete in RAII containers. Use unique_ptr for exclusive ownership.
OOM kill, need root cause post-mortem+
Immediate action
Check dmesg | tail -20 for OOM killer output. Find PID and see coredump.
Commands
coredumpctl list | grep myapp; coredumpctl gdb myapp COREDUMP
gdb -c core -ex 'info proc mappings' -ex 'thread apply all bt' -ex 'quit'
Fix now
Increase memory limits temporarily, then deploy fix. Add malloc_trim(0) periodic calls if glibc holds free memory.
Leak only in multi-threaded environment+
Immediate action
Run with ThreadSanitizer to detect data races on shared pointers. Add -fsanitize=thread,address.
Commands
valgrind --tool=helgrind ./myapp
ASAN_OPTIONS=detect_race=1 ./myapp
Fix now
Wrap shared mutable state in mutex. Use atomic shared_ptr for lock-free reads.
Memory Leak Detection Tools vs. Prevention Strategies
MethodOverheadLeak Types CaughtProduction SuitabilityEase of Use
Valgrind Memcheck10-50x slowdownAll definite/possible lossNot suitable (too slow)Medium
AddressSanitizer2-4x slowdownUse-after-free, leaks, overflowsPartial (canary instances)Easy (compile-time flag)
Core dump analysis0% overhead (post-mortem)Only if dump is completeYes (enable core dumps)Hard
Heap profiling (gperftools)~5% overheadGrowing allocations (not necessarily leaks)Yes (continuous)Medium
RAII + Smart Pointers0% (design-time)Prevents all ownership-related leaksAlwaysEasy
Static Analysis (clang-tidy)Compile-time onlyPattern-based leaksAlwaysEasy

Key takeaways

1
Memory leaks are unreachable heap allocations that cause RSS growth over time.
2
Use RAII and smart pointers to make leaks structurally impossible.
3
Valgrind and AddressSanitizer are your development debuggers; heap profilers for production.
4
Six patterns cause 90% of leaks
missing virtual destructors, circular shared_ptr, exception unsafety, reassignment, TLS, and missing cleanup.
5
Static analysis (clang-tidy, cppcheck) in CI prevents leaks before they ship.
6
Production debugging relies on core dumps and heap profiling
never run Valgrind or ASan on live traffic.

Common mistakes to avoid

5 patterns
×

Forgetting to delete in early return paths

Symptom
Leaks only under certain conditions (e.g., error handling). Memory grows when the application encounters specific errors.
Fix
Always use RAII or smart pointers for any heap allocation. Never use raw new/delete in code paths that may exit early.
×

Missing virtual destructor in base class

Symptom
Derived class destructor never called when deleting through base pointer. Resources in derived class leak silently.
Fix
Declare base destructor virtual. Use override and = default in derived. Enable -Wsuggest-override compiler flag.
×

Circular shared_ptr references

Symptom
Two objects hold shared_ptr to each other. Neither reference count reaches zero. Objects and their resources never freed.
Fix
Convert one direction to weak_ptr. For parent-child hierarchies, parent holds shared_ptr to child, child holds weak_ptr back to parent.
×

Reassigning a pointer without deleting the old value

Symptom
Leak on every assignment that overwrites a pointer before freeing its target. Common in setters, loops, and property updates.
Fix
Always delete before reassigning raw pointer, or use smart pointer (unique_ptr or shared_ptr) that automatically deletes old value on assignment.
×

Assuming memory is freed on thread exit without TLS cleanup

Symptom
Thread-local storage (TLS) allocated with pthread_setspecific may leak if the thread exits without calling pthread_setspecific(key, NULL) first.
Fix
Register a destructor function via pthread_key_create that frees the TLS block. Or use thread_local variables with RAII objects.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the difference between a memory leak and memory fragmentation. H...
Q02SENIOR
Can std::shared_ptr still leak? If so, how and how do you prevent it?
Q03SENIOR
What is the role of weak_ptr in preventing memory leaks? Provide a real-...
Q04SENIOR
How would you debug a memory leak in a multi-threaded C++ application th...
Q05SENIOR
What is a memory leak in C++? How does it differ from a buffer overflow?
Q01 of 05SENIOR

Explain the difference between a memory leak and memory fragmentation. How would you diagnose each in a production system?

ANSWER
A memory leak is allocated memory that is no longer reachable by any pointer — the heap manager cannot reclaim it. Fragmentation means free memory exists but is scattered in small blocks, so a large allocation request may fail even though total free space is sufficient. Diagnosis: - Leak: Use Valgrind or ASan (dev/test). In production, use heap profiling (gperftools, jemalloc) and diff snapshots. Look for RSS growth and missing deallocations. - Fragmentation: Query the allocator's free list stats (e.g., malloc_info(0, stderr) for glibc). Use mallctl for jemalloc. Look for many small free blocks. Also monitor RSS vs. virtual memory (VSZ) — large VSZ with moderate RSS often indicates fragmentation. Fix: Leaks are fixed by adding missing deletes or using RAII. Fragmentation is mitigated by using memory pools, slab allocators, or jemalloc's background coalescing.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can a program leak memory if all allocations use smart pointers?
02
What is the difference between 'definitely lost' and 'still reachable' in Valgrind output?
03
Does using a vector or other STL containers automatically prevent memory leaks?
04
Is it safe to call malloc_info(0, stderr) in production?
05
How can I detect memory leaks in a running production process without restarting it?
🔥

That's C++ Advanced. Mark it forged?

5 min read · try the examples if you haven't

Previous
Multithreading in C++
6 / 18 · C++ Advanced
Next
Design Patterns in C++