C++ Memory Leaks: Causes, Detection, and Prevention with Smart Pointers

C++ Memory Leaks: Causes, Detection, and Prevention with Smart Pointers

이 글의 핵심

Practical guide to C++ memory leaks: causes, tools, and RAII.

What is a memory leak?

Memory you allocated but never freed (or never release ownership of), so it stays allocated until process exit.

// ❌ Leak
void func() {
    int* ptr = new int(10);
}

// ✅ Correct cleanup
void func() {
    int* ptr = new int(10);
    delete ptr;
}

Common causes

// 1. Missing delete
int* ptr = new int(10);

// 2. Exception skips delete
void func() {
    int* ptr = new int(10);
    throw std::runtime_error("error");
    delete ptr;
}

// 3. Early return paths
int* func(bool flag) {
    int* ptr = new int(10);
    if (flag) {
        return ptr;
    }
    delete ptr;
    return nullptr;
}

// 4. shared_ptr cycles
class Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
};

Examples

Example 1: Basic leak vs smart pointer

#include <iostream>

class Widget {
public:
    Widget(int size) : size(size) {
        data = new int[size];
        std::cout << "Widget alloc: " << size << std::endl;
    }
    
    ~Widget() {
        delete[] data;
        std::cout << "Widget free: " << size << std::endl;
    }
    
private:
    int* data;
    int size;
};

void leak() {
    Widget* w = new Widget(100);
}

void noLeak() {
    auto w = std::make_unique<Widget>(100);
}

int main() {
    leak();
    noLeak();
}

Example 2: Exception safety

#include <stdexcept>

void processData(bool throwError) {
    int* data = new int[1000];
    
    if (throwError) {
        throw std::runtime_error("error");
    }
    
    delete[] data;
}

void processDataSafe(bool throwError) {
    auto data = std::make_unique<int[]>(1000);
    
    if (throwError) {
        throw std::runtime_error("error");
    }
}

int main() {
    try {
        processDataSafe(true);
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }
}

Example 3: Containers of raw pointers

#include <vector>

class Resource {
public:
    Resource() {
        data = new int[100];
    }
    
    ~Resource() {
        delete[] data;
    }
    
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
    
private:
    int* data;
};

void leak() {
    std::vector<Resource*> resources;
    for (int i = 0; i < 10; i++) {
        resources.push_back(new Resource());
    }
}

void noLeak() {
    std::vector<std::unique_ptr<Resource>> resources;
    for (int i = 0; i < 10; i++) {
        resources.push_back(std::make_unique<Resource>());
    }
}

Example 4: Circular references

#include <memory>

class Node {
public:
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
};

void circularReference() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    node1->next = node2;
    node2->prev = node1;
}

class NodeFixed {
public:
    std::shared_ptr<NodeFixed> next;
    std::weak_ptr<NodeFixed> prev;
};

Detection

// Valgrind: valgrind --leak-check=full ./program
// ASan: g++ -fsanitize=address -g program.cpp
// MSVC debug: _CrtSetDbgFlag(...)

Common pitfalls

Pitfall 1: delete vs delete[]

int* arr = new int[10];
delete[] arr;

auto arr = std::make_unique<int[]>(10);

Pitfall 2: Double delete

delete ptr;
ptr = nullptr;
delete ptr;  // safe no-op on nullptr

Pitfall 3: Unclear ownership

void func(std::unique_ptr<int> ptr) { }

std::unique_ptr<int> getData() {
    return std::make_unique<int>(10);
}

Pitfall 4: vector::clear on raw pointers

for (auto ptr : vec) {
    delete ptr;
}
vec.clear();

std::vector<std::unique_ptr<int>> vec;
vec.clear();

Prevention

auto ptr = std::make_unique<int>(10);
std::vector<int> vec(10);

FAQ

Q1: When do leaks happen?

A: Missing deletes, exceptions, cycles, unclear ownership.

Q2: How to detect?

A: Valgrind, AddressSanitizer, IDE leak tools.

Q3: How to prevent?

A: Smart pointers, RAII, avoid raw new in app code.

Q4: Performance impact?

A: Growing RSS, slower allocation, eventual instability.

Q5: Which smart pointer?

A: unique_ptr for sole ownership, shared_ptr + weak_ptr for shared graphs.

Q6: Resources?

A: Effective C++, C++ Core Guidelines, Valgrind docs.


See also

  • C++ use-after-free
  • C++ heap corruption
  • C++ Valgrind

Practical tips

Debugging

  • Enable warnings
  • Minimize repro cases

Performance

  • Profile hotspots
  • Define metrics

Code review

  • Follow conventions

Checklist

Before coding

  • Right approach?
  • Team can maintain?
  • Performance OK?

While coding

  • Warnings fixed?
  • Edge cases?
  • Errors handled?

At review

  • Clear intent?
  • Tests?
  • Docs?

Keywords

C++, memory leak, memory, debugging, RAII, smart pointers.


  • C++ debugging techniques
  • C++ heap corruption
  • C++ sanitizers
  • C++ use-after-free
  • C++ Valgrind