SrcForge

Lesson 64

Memory Leak Detection

Covers causes of memory leaks, types of leaks, Valgrind, AddressSanitizer, LeakSanitizer, smart pointers, and best practices for preventing leaks.

Lesson content

What is Memory Leak Detection in C++?

Memory Leak Detection in C++ is the process of finding memory that your program allocates but never releases. A memory leak occurs when dynamically allocated memory remains in use even though the program no longer has a way to access or free it.

Imagine renting hotel rooms for guests. When guests check out, you should return the room keys so the hotel can use the rooms again. If you keep renting rooms but never return the keys, the hotel eventually runs out of available rooms. Memory leaks work the same way, your program keeps reserving memory but never gives it back to the operating system.

Memory leaks can cause applications to consume more and more memory over time, eventually slowing down or even crashing the system.

Why does memory leak detection exist?

Dynamic memory management gives programmers flexibility, but it also introduces responsibility. If allocated memory is not released correctly, it remains reserved until the program terminates. Memory leak detection tools help developers identify these problems before software reaches production.

Real-world use cases

  • Long-running server applications
  • Desktop applications
  • Embedded systems
  • Game engines
  • Database systems
  • Scientific computing
  • Network services
  • Large-scale enterprise software

Prerequisites

  • Variables and data types
  • Pointers
  • References
  • Dynamic memory (new and delete)
  • Functions
  • Basic command-line usage
  • Basic debugging concepts

What is a memory leak?

A memory leak happens when dynamically allocated memory is not released after it is no longer needed.

int* ptr = new int(100);

// Missing delete statement

Since delete ptr; is never called, the allocated memory becomes unreachable and remains allocated.

What causes memory leaks?

  • Forgetting delete — allocated memory is never released
  • Losing a pointer — original pointer is overwritten
  • Exceptions — function exits before cleanup
  • Circular references — common with improperly used shared_ptr
  • Early returns — function exits before freeing memory
  • Resource ownership confusion — multiple objects manage the same resource incorrectly

Types of memory leaks

A definite leak occurs when memory is allocated but never freed, such as when the original pointer is reassigned before it is deleted. An indirect leak occurs when leaked memory contains pointers to other allocated memory. A possible leak is one where the debugger cannot determine whether memory is still accessible. Memory that is still reachable when the program exits is not always a programming error, since some libraries intentionally keep allocated memory until program termination.

int* p = new int;

p = nullptr;

// The original memory can no longer be accessed

Tools for memory leak detection

  • Valgrind (Linux) — detects leaks and invalid memory operations
  • AddressSanitizer / ASan (Linux, macOS, Windows) — detects memory errors
  • LeakSanitizer / LSan (Linux, macOS) — detects memory leaks
  • Visual Studio Diagnostic Tools (Windows) — detects leaks and memory usage
  • Dr. Memory (Windows/Linux) — memory debugging tool

Using Valgrind

# Compile with debugging information
g++ -g -O0 main.cpp -o program

# Run Valgrind
valgrind --leak-check=full ./program

Useful options include --leak-check=full for a detailed leak report, --show-leak-kinds=all to show all leak types, --track-origins=yes to find the origin of invalid values, and --verbose to display additional information.

Using AddressSanitizer

# Compile
g++ -g -fsanitize=address main.cpp -o program

# Run normally
./program

AddressSanitizer automatically reports many memory errors, including heap buffer overflows and use-after-free bugs.

Using LeakSanitizer

LeakSanitizer is often enabled automatically with AddressSanitizer on supported platforms. Leak reports are displayed when the program exits.

g++ -g -fsanitize=address -fno-omit-frame-pointer main.cpp -o program

Example 1: Simple memory leak

#include <iostream>
using namespace std;

int main()
{
    int* number = new int(25);

    cout << *number << endl;

    return 0;
}

// Compile:
// g++ -g -O0 main.cpp -o program

// Detect:
// valgrind --leak-check=full ./program

Example 2: Fixing the leak

#include <iostream>
using namespace std;

int main()
{
    int* number = new int(25);

    cout << *number << endl;

    delete number;

    number = nullptr;

    return 0;
}

// Valgrind should now report:
// All heap blocks were freed -- no leaks are possible

Example 3: Lost pointer

#include <iostream>
using namespace std;

int main()
{
    int* ptr = new int(10);

    ptr = new int(20);

    delete ptr;

    return 0;
}

// Valgrind reports a definitely lost memory block

Example 4: Exception safety

#include <iostream>
#include <stdexcept>
using namespace std;

void process()
{
    int* data = new int(50);

    throw runtime_error("Error");

    delete data;
}

int main()
{
    try
    {
        process();
    }
    catch (...)
    {
        cout << "Exception handled";
    }

    return 0;
}

The allocated memory is leaked because control leaves the function before delete executes.

Example 5: Smart pointer solution

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    unique_ptr<int> value = make_unique<int>(100);

    cout << *value << endl;

    return 0;
}

// Output: 100

std::unique_ptr automatically releases memory when it goes out of scope, making this approach safer than manual memory management.

Common mistakes and pitfalls

  • Forgetting delete. Fix: always release allocated memory or use smart pointers
  • Overwriting a pointer. Fix: free memory before assigning a new address
  • Using raw pointers everywhere. Fix: prefer std::unique_ptr or std::shared_ptr
  • Ignoring Valgrind reports. Fix: investigate every reported leak
  • Returning early without cleanup. Fix: use RAII (Resource Acquisition Is Initialization)
// Wrong
int* p = new int(5);

p = nullptr;

// Correct
int* p = new int(5);

delete p;

p = nullptr;

Best practices

  • Prefer RAII (Resource Acquisition Is Initialization) to manage resources automatically
  • Use std::unique_ptr for exclusive ownership of dynamically allocated objects
  • Use std::shared_ptr only when shared ownership is required
  • Avoid calling new and delete directly unless necessary
  • Run Valgrind or AddressSanitizer regularly during development
  • Compile with -g -O0 while debugging memory issues
  • Review every leak report, even if the leaked memory is small
  • Use standard library containers (std::vector, std::string, std::map) instead of manually managed dynamic arrays whenever possible

When not to use this

Memory leak detection tools are invaluable, but they are not the best tool for every task. Avoid relying on them when investigating compile-time errors, when measuring application performance (use a profiler instead), when diagnosing purely logical bugs that do not involve memory, or when working with code where the issue is unrelated to resource management. Also note that tools such as Valgrind can slow program execution significantly, making them less suitable for performance benchmarking.

Summary

  • A memory leak occurs when dynamically allocated memory is never released
  • Common causes include missing delete, lost pointers, exceptions, and ownership mistakes
  • Valgrind provides detailed memory leak reports on Linux
  • AddressSanitizer and LeakSanitizer detect many memory-related issues with compiler support
  • Smart pointers and RAII greatly reduce the likelihood of memory leaks
  • Standard library containers help eliminate the need for manual memory management

Frequently asked questions

What is a memory leak in C++?

A memory leak occurs when dynamically allocated memory is no longer needed but is never released, causing the application to consume increasing amounts of memory over time.

What is the difference between Valgrind and AddressSanitizer?

Valgrind analyzes programs at runtime without recompilation (beyond debug symbols), while AddressSanitizer requires compilation with sanitizer options and generally runs much faster, though each tool has different strengths.

Can smart pointers prevent memory leaks?

Yes. Smart pointers such as std::unique_ptr automatically release memory when it is no longer needed, greatly reducing the risk of leaks.

Why should I use RAII?

RAII ties resource management to object lifetime. When an object goes out of scope, its destructor automatically releases owned resources, even if an exception occurs.

Should I still use memory leak detection tools if I use smart pointers?

Yes. Although smart pointers eliminate many leaks, memory analysis tools can still detect issues such as incorrect ownership, circular references with std::shared_ptr, and other memory-related bugs.