[2026] C++ Memory Leaks | Real Server Outage Cases and Five Patterns Valgrind Catches

[2026] C++ Memory Leaks | Real Server Outage Cases and Five Patterns Valgrind Catches

이 글의 핵심

C++ memory leaks: new/delete pitfalls, smart pointers, Valgrind and AddressSanitizer. Early returns, double free, delete[] mistakes—fix leaks before they take down production servers.

Introduction: Friday 5 PM—the server stopped responding

A memory leak took down our server

Two weeks after launch, Friday 5 PM, the server stopped responding. Restart fixed it until every 2–3 hours it froze again. What we checked:

  • CPU: ~10% (fine)
  • Disk: 50GB free (fine)
  • Memory: 500MB at start → 7.8GB in 3 hours → crash Cause: memory leak—allocated memory never freed, like a dripping pipe until the tank overflows. Flow: allocate → miss delete on error paths → leak → OOM. Fix mindset: RAII ties allocation to object lifetime—like an automatic door that closes when you leave the room. std::unique_ptr and containers own their memory and release on scope exit, so you do not hunt every return for a matching delete. 다음은 mermaid를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
flowchart LR
  subgraph cause[Cause]
    N[new without]
    R[return/exception]
    N --> R
    R --> L[missed delete]
  end
  subgraph detect[Detect]
    V[Valgrind]
    A[AddressSanitizer]
  end
  subgraph fix[Fix]
    U[unique_ptr]
    RAII[RAII]
  end
  cause --> detect --> fix

Somewhere we new’d and never delete’d; memory grew until the process died. Buggy pattern (found after 3 days): 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 실행 예제
void processRequest(const std::string& data) {
    User* user = new User(data);  // heap allocation
    if (!user->isValid()) {
        return;  // no delete — leak!
    }
    user->process();
    delete user;  // only on happy path
}

Fix (paste and build: g++ -std=c++17 -o leak_fix leak_fix.cpp && ./leak_fix): 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Paste after: g++ -std=c++17 -o leak_fix leak_fix.cpp && ./leak_fix
#include <memory>
#include <iostream>
#include <string>
struct User {
    std::string data;
    explicit User(const std::string& d) : data(d) {}
    bool isValid() const { return !data.empty() && data != "invalid"; }
    void process() { std::cout << "processed " << data << "\n"; }
};
void processRequest(const std::string& data) {
    auto user = std::make_unique<User>(data);
    if (!user->isValid()) {
        return;  // automatic cleanup
    }
    user->process();
}
int main() {
    processRequest("hello");
    processRequest("invalid");
    return 0;
}

Output: processed hello only (invalid returns early with no extra output). Takeaway: Prefer std::unique_ptr / std::make_unique so ownership is clear and every path frees memory. See smart pointers. After reading:

  • Understand risky new/delete patterns
  • Detect and fix leaks
  • Learn production-style bug stories
  • Use Valgrind and AddressSanitizer

More scenarios

  • Game server: removePlayer() skipped → Player* leaks → OOM hours later.
  • Image batch: new unsigned char[...] + throw on parse error → many buffers leaked.
  • LRU cache: map<Key, Value*> evicts with erase only → objects not deleted.
  • Callbacks: new Callback() registered, never unregistered → leak.

Table of contents

  1. How new and delete work
  2. Five dangerous patterns
  3. Real leak cases
  4. Examples and detection
  5. Detection tools
  6. Debugging practice
  7. Common leak patterns
  8. Errors and fixes
  9. Best practices
  10. Prevention: smart pointers

1. How new and delete work

What new does

  1. Allocate with operator new
  2. Call constructor
  3. Return pointer—you must delete exactly once (or use smart pointers).

What delete does

  1. Call destructor
  2. Release memory with operator delete

Never mix malloc/free with C++ objects

Use new/delete so constructors/destructors run—avoid malloc/free for C++ objects.

2. Five dangerous patterns

1. Double delete

int* ptr = new int(42);
delete ptr;
delete ptr;  // undefined behavior

Fix: delete ptr; ptr = nullptr; or use smart pointers.

2. Dangling pointer

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

int* ptr1 = new int(42);
int* ptr2 = ptr1;
delete ptr1;
ptr1 = nullptr;
std::cout << *ptr2;  // use-after-free

Fix: shared_ptr or clear ownership rules.

3. Leak on early return

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

void function() {
    int* ptr = new int(42);
    if (someCondition) {
        return;  // leak
    }
    delete ptr;
}

Fix: std::unique_ptr.

4. delete vs delete[]

int* arr = new int[100];
delete arr;  // wrong — use delete[]

Correct: delete[] array; or std::vector / make_unique<int[]>(n).

5. Exception safety

아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

void processFile(const std::string& filename) {
    char* buffer = new char[1024];
    std::ifstream file(filename);
    if (!file) {
        throw std::runtime_error("File not found");
        // delete[] never runs
    }
    file.read(buffer, 1024);
    delete[] buffer;
}

Fix:

auto buffer = std::make_unique<char[]>(1024);
// ...

3. Real cases

Vector of raw pointers

vector.clear() does not delete pointed-to objects—only vector<unique_ptr<T>> or manual loop.

Conditional returns

Every path that allocates must free—or use RAII/unique_ptr.

Exceptions between new and delete

Use smart pointers or vector so unwinding always calls destructors.

4. Examples and detection

Early-return leak + Valgrind

Compile with -g, run:

valgrind --leak-check=full --show-leak-kinds=all ./leak_early_return

Look for definitely lost and the stack trace.

delete vs delete[] + LeakSanitizer

g++ -fsanitize=address,leak -g -std=c++17 -o leak_array leak_array.cpp
./leak_array

Container of pointers

clear() without deleting each Item* → Valgrind reports many lost blocks.

5. Detection tools

Valgrind

g++ -g program.cpp -o program
valgrind --leak-check=full --show-leak-kinds=all ./program

Interpret definitely lost, Invalid read, etc.

AddressSanitizer (+ LeakSanitizer)

g++ -fsanitize=address,leak -g program.cpp -o program
./program

VS CRT debug heap (Windows)

_CrtSetDbgFlag / leak check on exit—see MSVC docs.

6. Debugging practice

  1. Confirm RSS grows over time (top, ps)
  2. Run Valgrind/ASan with representative workload
  3. Jump to reported line, fix ownership
  4. Re-run until clean

7. Common patterns

  • Factory returning T* → return unique_ptr<T>
  • Exception between new/delete → RAII
  • map of pointers → unique_ptr values or explicit delete on erase
  • shared_ptr cycles → weak_ptr

8. Errors and fixes

  • definitely lost: add matching delete or smart pointer
  • invalid free / double free: one owner, one delete
  • heap-use-after-free: lifetime bug—fix ordering, use shared_ptr/weak_ptr
  • Mismatched free/delete: pair newdelete, new[]delete[], mallocfree

9. Best practices

PrincipleBadGood
Allocationnew Tmake_unique<T>()
Arraysnew T[n]vector<T> or make_unique<T[]>(n)
Ownershipreturn new T()return make_unique<T>()
Containersvector<T*>vector<unique_ptr<T>>
CI with ASan (-fsanitize=address,leak) on PRs is highly recommended.

10. Prevention: smart pointers

unique_ptr fixes leaks, exception paths, and most double-delete issues on single ownership. See next article.

Keywords

C++ memory leak, new delete, Valgrind, AddressSanitizer, dangling pointer, double free, delete[], leak detection

Closing

  • new/delete are risky—prefer smart pointers and containers
  • Valgrind + ASan catch leaks and heap bugs
  • delete then nullptr helps avoid double delete (raw pointers)
  • delete[] for arrays
  • Exception safety: RAII Next: C++ practical guide #6-3: smart pointers

FAQ

When is this useful?

A. Whenever you manage heap memory in C++—servers, games, long-running tools. Use the examples and tool guide above.

What to read first?

A. Stack vs heap and the series index.

Go deeper?

A. cppreference memory, Valgrind and ASan docs. One-line summary: Replace raw new/delete with smart pointers; use Valgrind and ASan to prove leaks are gone.

Practical tips

Debugging

  • Enable warnings; reproduce with a tiny test case

Performance

  • Profile before optimizing; define metrics

Code review

  • Every new has an owner; every path frees or uses RAII

References


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3