C++ Valgrind: A Practical Memory Debugging Guide
이 글의 핵심
A hands-on guide to C++ Valgrind for memory debugging and profiling.
Introduction
Valgrind is a powerful toolkit for memory leaks, bugs, and profiling in C/C++ programs. On Linux and macOS it is essential for tracking down memory issues.
1. Installation and basic usage
Installation
# Ubuntu/Debian
sudo apt-get install valgrind
# macOS
brew install valgrind
# Check version
valgrind --version
Basic usage
# Compile (debug symbols)
g++ -g program.cpp -o program
# Run Valgrind
valgrind --leak-check=full ./program
# More detail
valgrind --leak-check=full --show-leak-kinds=all ./program
# Track origins of uninitialized values
valgrind --track-origins=yes ./program
2. Detecting memory leaks
Example 1: Memory leak
// leak.cpp
#include <iostream>
int main() {
int* ptr = new int(42);
std::cout << *ptr << std::endl;
return 0;
}
g++ -g leak.cpp -o leak
valgrind --leak-check=full ./leak
Sample output:
==12345== HEAP SUMMARY:
==12345== in use at exit: 4 bytes in 1 blocks
==12345== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==12345==
==12345== 4 bytes in 1 blocks are definitely lost
==12345== at 0x4C2E0EF: operator new(unsigned long)
==12345== by 0x400B2C: main (leak.cpp:5)
Example 2: Uninitialized memory
// uninit.cpp
#include <iostream>
int main() {
int x;
if (x > 0) {
std::cout << "positive" << std::endl;
}
return 0;
}
g++ -g uninit.cpp -o uninit
valgrind --track-origins=yes ./uninit
Sample output:
==12345== Conditional jump or move depends on uninitialised value(s)
==12345== at 0x400B2C: main (uninit.cpp:6)
Example 3: Invalid memory access
// invalid.cpp
#include <iostream>
int main() {
int arr[10];
for (int i = 0; i <= 10; ++i) {
arr[i] = i;
}
return 0;
}
g++ -g invalid.cpp -o invalid
valgrind ./invalid
Sample output:
==12345== Invalid write of size 4
==12345== at 0x400B2C: main (invalid.cpp:7)
3. Valgrind tools
Memcheck
valgrind --tool=memcheck ./program
Cachegrind
valgrind --tool=cachegrind ./program
cg_annotate cachegrind.out.12345
Callgrind
valgrind --tool=callgrind ./program
kcachegrind callgrind.out.12345
Helgrind
valgrind --tool=helgrind ./program
Massif
valgrind --tool=massif ./program
ms_print massif.out.12345
4. Common issues
Issue 1: Performance
# Valgrind is very slow (often 10–50×)
# Use only in development/testing
# Test with small inputs
valgrind --leak-check=full ./program < small_input.txt
Issue 2: False positives
# Generate suppression rules
valgrind --gen-suppressions=all ./program > my.supp
# Use suppressions
valgrind --suppressions=my.supp ./program
Example my.supp:
{
<system_library_leak>
Memcheck:Leak
fun:malloc
fun:system_function
}
Issue 3: Missing debug info
# ❌ No debug symbols
g++ program.cpp -o program
valgrind ./program
# ✅ Use -g
g++ -g program.cpp -o program
valgrind ./program
Issue 4: Optimization level
# ❌ Heavy optimization
g++ -O3 -g program.cpp
# ✅ Lower optimization for clearer stacks
g++ -O0 -g program.cpp
5. Interpreting output
Leak kinds
# Definite leak
definitely lost: 100 bytes in 5 blocks
# Indirect leak
indirectly lost: 50 bytes in 2 blocks
# Possible leak
possibly lost: 20 bytes in 1 blocks
# Still reachable at exit
still reachable: 30 bytes in 3 blocks
Quick reference
| Kind | Meaning | Action |
|---|---|---|
| definitely lost | True leak | Fix |
| indirectly lost | Leak via parent pointer | Fix parent leak |
| possibly lost | Possible leak | Investigate |
| still reachable | Still pointed to at exit | Often OK; free on shutdown if strict |
6. Practical examples
Example 1: Smart pointers
// smart_ptr.cpp
#include <memory>
#include <iostream>
int main() {
auto ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl;
return 0;
}
g++ -g smart_ptr.cpp -o smart_ptr
valgrind --leak-check=full ./smart_ptr
Sample output:
==12345== HEAP SUMMARY:
==12345== in use at exit: 0 bytes in 0 blocks
==12345== total heap usage: 1 allocs, 1 frees, 4 bytes allocated
==12345==
==12345== All heap blocks were freed -- no leaks are possible
Example 2: Vector of raw pointers
// vector_leak.cpp
#include <vector>
#include <iostream>
int main() {
std::vector<int*> vec;
for (int i = 0; i < 5; i++) {
vec.push_back(new int(i));
}
return 0;
}
g++ -g vector_leak.cpp -o vector_leak
valgrind --leak-check=full ./vector_leak
Fix:
// vector_fixed.cpp
#include <vector>
#include <iostream>
int main() {
std::vector<int*> vec;
for (int i = 0; i < 5; i++) {
vec.push_back(new int(i));
}
for (auto ptr : vec) {
delete ptr;
}
return 0;
}
Summary
Key takeaways
- Valgrind: Memory debugging and profiling framework
- Memcheck: Memory error and leak detection
- Cachegrind: Cache profiling
- Helgrind: Threading errors
- Cost: Often 10–50× slower
Tool comparison
| Tool | Purpose | Typical slowdown |
|---|---|---|
| Memcheck | Memory errors | 10–50× |
| Cachegrind | Cache analysis | 20–100× |
| Callgrind | Call graphs | 20–100× |
| Helgrind | Thread errors | 20–50× |
| Massif | Heap profiling | ~20× |
Practical tips
- Compile with
-g - Use
--leak-check=full - Test with small inputs
- Use suppression files for known false positives
- Prefer smart pointers
Next steps
- C++ Sanitizers
- C++ GDB
- C++ memory management
Related posts
- C++ debugging techniques | GDB, sanitizers, leaks, multithreading