[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
deleteon 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 everyreturnfor a matchingdelete. 다음은 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/deletepatterns - 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[...]+throwon parse error → many buffers leaked. - LRU cache:
map<Key, Value*>evicts witheraseonly → objects not deleted. - Callbacks:
new Callback()registered, never unregistered → leak.
Table of contents
- How
newanddeletework - Five dangerous patterns
- Real leak cases
- Examples and detection
- Detection tools
- Debugging practice
- Common leak patterns
- Errors and fixes
- Best practices
- Prevention: smart pointers
1. How new and delete work
What new does
- Allocate with
operator new - Call constructor
- Return pointer—you must
deleteexactly once (or use smart pointers).
What delete does
- Call destructor
- 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
- Confirm RSS grows over time (
top,ps) - Run Valgrind/ASan with representative workload
- Jump to reported line, fix ownership
- Re-run until clean
7. Common patterns
- Factory returning
T*→ returnunique_ptr<T> - Exception between
new/delete→ RAII mapof pointers →unique_ptrvalues or explicit delete on eraseshared_ptrcycles →weak_ptr
8. Errors and fixes
- definitely lost: add matching
deleteor 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: pairnew↔delete,new[]↔delete[],malloc↔free
9. Best practices
| Principle | Bad | Good |
|---|---|---|
| Allocation | new T | make_unique<T>() |
| Arrays | new T[n] | vector<T> or make_unique<T[]>(n) |
| Ownership | return new T() | return make_unique<T>() |
| Containers | vector<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.
Related posts
Keywords
C++ memory leak, new delete, Valgrind, AddressSanitizer, dangling pointer, double free, delete[], leak detection
Closing
new/deleteare risky—prefer smart pointers and containers- Valgrind + ASan catch leaks and heap bugs
deletethennullptrhelps 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
newhas an owner; every path frees or uses RAII