C & C++
Valgrind Cheat Sheet
From Complete Beginner to Advanced
1. What is Valgrind?
# Valgrind is a dynamic analysis tool for Linux/macOS programs.
# It detects memory errors, leaks, threading bugs, and performance issues.
# It runs your program inside a virtual CPU — no recompilation needed.
# (But compile with -g for useful line numbers in reports)
# Install on Debian/Ubuntu
sudo apt install valgrind
# Install on macOS (limited support — Linux preferred)
brew install valgrind
# Verify installation
valgrind --version
# → valgrind-3.22.0Beginner
2. Compiling for Valgrind
# Always compile with -g (debug symbols) for readable output
# Optionally use -O0 to disable optimizations (more accurate results)
gcc -g -O0 -o myprogram myprogram.c
# For C++
g++ -g -O0 -o myprogram myprogram.cpp3. Basic Run (Memcheck — Default Tool)
# Simplest usage — Memcheck tool runs by default
valgrind ./myprogram
# With command-line arguments for your program
valgrind ./myprogram arg1 arg24. Understanding the Output Format
# Every Valgrind line is prefixed with ==PID==
# Example output:
==12345== Memcheck, a memory error detector
==12345== Invalid read of size 4 ← type of error
==12345== at 0x401234: main (foo.c:10) ← where it happened
==12345== Address 0x... is 0 bytes after ← what memory was touched
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 40 bytes in 1 blocks ← real leak
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 0 bytes in 0 blocks ← maybe leak
==12345== still reachable: 0 bytes in 0 blocks ← pointer exists but not freed
==12345== suppressed: 0 bytes in 0 blocks ← ignored (by suppression rules)5. Detecting Memory Leaks
// myprogram.c — intentional leak
#include <stdlib.h>
int main() {
int* p = malloc(40); // Allocated but never freed — leak!
return 0;
}# Run with leak checking enabled
valgrind --leak-check=full ./myprogram
# → Reports "definitely lost: 40 bytes in 1 blocks"
# --leak-check=full shows each leak's allocation stack trace
# --leak-check=yes summary only
# --leak-check=no disable leak check6. Detecting Invalid Memory Access
// Reading beyond array bounds
int main() {
int arr[5];
return arr[10]; // Invalid read — out of bounds!
}valgrind ./myprogram
# → "Invalid read of size 4"
# → Shows the exact file and line number with -g7. Detecting Use-After-Free
int main() {
int* p = malloc(sizeof(int));
free(p);
*p = 42; // Use after free — undefined behavior!
return 0;
}valgrind ./myprogram
# → "Invalid write of size 4"
# → "Address ... is 0 bytes inside a block of size 4 free'd"8. Detecting Uninitialized Memory
int main() {
int x; // Declared but never initialized
return x + 1; // Using uninitialized value!
}valgrind --track-origins=yes ./myprogram
# → "Use of uninitialised value of size 4"
# → --track-origins=yes tells you WHERE the uninit value came fromIntermediate
9. Commonly Used Flags
# Show full leak details with allocation stack traces
valgrind --leak-check=full ./myprogram
# Also show reachable and indirectly lost blocks
valgrind --leak-check=full --show-leak-kinds=all ./myprogram
# Track where uninitialized values originate
valgrind --track-origins=yes ./myprogram
# Show error details verbosely
valgrind -v ./myprogram
# Limit number of errors reported (default: 10 per unique spot)
valgrind --error-limit=no ./myprogram
# Combine all useful flags for thorough checking
valgrind --leak-check=full \\
--show-leak-kinds=all \\
--track-origins=yes \\
--error-limit=no \\
-v \\
./myprogram10. Saving Output to a File
# Redirect Valgrind output to a log file
valgrind --log-file=valgrind_report.txt ./myprogram
# Append PID to filename (useful for multi-process apps)
valgrind --log-file=report_%p.txt ./myprogram
# → Creates report_12345.txt etc.
# Redirect to a file descriptor (e.g., stderr = 2)
valgrind --log-fd=2 ./myprogram11. Suppression Files (Ignore Known False Positives)
# Generate a suppression file from current errors
valgrind --gen-suppressions=all ./myprogram 2> my.supp
# → Prints suppression blocks you can copy into a .supp file
# Use a suppression file to ignore known issues
valgrind --suppressions=my.supp ./myprogram
# Use multiple suppression files
valgrind --suppressions=sys.supp --suppressions=my.supp ./myprogram# Example suppression block in a .supp file:
{
ignore_openssl_leak # Name (can be anything)
Memcheck:Leak # Tool:ErrorType
fun:malloc # Stack frame pattern
fun:CRYPTO_malloc
fun:*openssl*
}12. Callgrind — CPU Profiling
# Run with Callgrind to collect call graph + instruction counts
valgrind --tool=callgrind ./myprogram
# → Creates callgrind.out.<PID> file
# View the report in terminal
callgrind_annotate callgrind.out.12345
# Open visually with KCachegrind (GUI)
kcachegrind callgrind.out.12345
# Focus on specific function only (reduce noise)
valgrind --tool=callgrind --fn-skip=pthread_* ./myprogram
# Start profiling paused — toggle with callgrind_control
valgrind --tool=callgrind --instr-atstart=no ./myprogram
callgrind_control -i on # Start collecting
callgrind_control -i off # Stop collecting13. Helgrind — Thread Error Detector
# Detect race conditions, lock order violations, pthread API misuse
valgrind --tool=helgrind ./myprogram
# Common errors reported:
# → "Possible data race during read/write of size N"
# → "Lock order violated" (deadlock risk)
# → "Mutex is already locked by this thread"// Example race condition Helgrind catches:
int counter = 0; // Shared variable — no lock!
void* increment(void* arg) {
counter++; // ← Helgrind flags this as a data race
return NULL;
}14. DRD — Another Thread Checker
# DRD is an alternative to Helgrind — sometimes catches different issues
valgrind --tool=drd ./myprogram
# Key differences from Helgrind:
# → Lower memory overhead
# → Better support for custom synchronization primitives
# → Reports "Conflicting accesses" for races
# Show stack trace for both conflicting accesses
valgrind --tool=drd --show-stack-usage=yes ./myprogram15. Massif — Heap Profiler
# Profile heap memory usage over time
valgrind --tool=massif ./myprogram
# → Creates massif.out.<PID>
# View the report as a text chart
ms_print massif.out.12345
# → ASCII chart of heap usage over time
# Open visually with Massif-Visualizer (GUI)
massif-visualizer massif.out.12345
# Include stack memory in profiling
valgrind --tool=massif --pages-as-heap=yes ./myprogram
# Take snapshots every N allocations (default: 10)
valgrind --tool=massif --detailed-freq=1 ./myprogram16. Cachegrind — Cache & Branch Profiler
# Simulate CPU cache behavior — find cache misses
valgrind --tool=cachegrind ./myprogram
# → Creates cachegrind.out.<PID>
# View the annotated report
cg_annotate cachegrind.out.12345
# Diff two runs (to compare optimizations)
cg_diff cachegrind.out.111 cachegrind.out.222
# Output includes:
# Ir = Instructions read
# I1mr = L1 instruction cache misses
# D1mr = L1 data cache read misses
# DLmr = Last-level cache read missesAdvanced
17. Valgrind Client Requests (In-Code API)
#include <valgrind/valgrind.h>
#include <valgrind/memcheck.h>
int main() {
// Check if we're running under Valgrind
if (RUNNING_ON_VALGRIND) {
printf("Running under Valgrind\\n");
}
char buf[10];
// Mark memory as defined (suppress false positives for custom allocators)
VALGRIND_MAKE_MEM_DEFINED(buf, 10);
// Mark memory as undefined (trigger errors if accessed)
VALGRIND_MAKE_MEM_UNDEFINED(buf, 10);
// Mark memory as inaccessible (like freed memory)
VALGRIND_MAKE_MEM_NOACCESS(buf, 10);
// Print current memory error count to Valgrind log
unsigned errs = VALGRIND_COUNT_ERRORS;
// Force Valgrind to do a full leak check at this point
VALGRIND_DO_LEAK_CHECK;
return 0;
}# Compile with Valgrind headers
gcc -g -I/usr/include/valgrind -o myprogram myprogram.c18. Custom Memory Pool / Allocator Support
#include <valgrind/memcheck.h>
// Tell Valgrind about a custom memory pool
void* pool = malloc(1024); // Your pool buffer
// Register the pool
VALGRIND_CREATE_MEMPOOL(pool, 0, 0);
// When you hand out a block from your pool:
void* block = pool; // Your allocation logic
VALGRIND_MEMPOOL_ALLOC(pool, block, 64); // Mark 64 bytes as allocated
// When you "free" a block back to your pool:
VALGRIND_MEMPOOL_FREE(pool, block);
// When destroying the whole pool:
VALGRIND_DESTROY_MEMPOOL(pool);
free(pool);19. Multi-Process and Child Process Tracing
# Also trace child processes spawned by your program
valgrind --trace-children=yes ./myprogram
# Filter which children to trace by executable name
valgrind --trace-children=yes \\
--trace-children-skip=*bash*,*sh \\
./myprogram
# Log each child to its own file (using %p = PID placeholder)
valgrind --trace-children=yes \\
--log-file=vg_%p.txt \\
./myprogram20. Attaching to a Running Process (Vgdb)
# Start program under Valgrind with GDB server enabled
valgrind --vgdb=yes --vgdb-error=0 ./myprogram
# → Valgrind pauses and waits for GDB to connect
# In another terminal, attach GDB
gdb ./myprogram
(gdb) target remote | vgdb # Connect to the Valgrind GDB server
# Now you can set breakpoints, inspect memory, etc.
(gdb) monitor leak_check full # Run Valgrind commands via GDB monitor
(gdb) monitor who_points_at 0xADDRESS # Find what points to an address
(gdb) continue21. XML Output (for CI/CD Integration)
# Output errors as XML — great for parsing in CI pipelines
valgrind --xml=yes --xml-file=valgrind_out.xml ./myprogram
# Parse in Python (example)
# import xml.etree.ElementTree as ET
# tree = ET.parse('valgrind_out.xml')
# errors = tree.findall('.//error')
# Use with tools like:
# → valgrind-xml-parser (Python)
# → Jenkins Valgrind Plugin
# → GitLab CI artifact parsing22. Valgrind in CI/CD (GitHub Actions Example)
# .github/workflows/valgrind.yml
name: Valgrind Memory Check
on: [push, pull_request]
jobs:
memcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Valgrind
run: sudo apt-get install -y valgrind
- name: Build with debug symbols
run: gcc -g -O0 -o myprogram myprogram.c
- name: Run Valgrind
run: |
valgrind --error-exitcode=1 \\ # Exit with code 1 on any error
--leak-check=full \\
--show-leak-kinds=all \\
./myprogram
# CI fails automatically if Valgrind finds errors23. Exit Code Control (for Scripting)
# Exit with a non-zero code if Valgrind finds errors (great for CI)
valgrind --error-exitcode=1 ./myprogram
echo $? # → 1 if errors found, 0 if clean
# Set a specific exit code value
valgrind --error-exitcode=42 ./myprogram24. Performance Tips
# Valgrind slows programs by 10x–50x — reduce with these:
# Reduce detail level (faster but less info)
valgrind --leak-check=summary ./myprogram
# Skip unneeded checks
valgrind --undef-value-errors=no ./myprogram # Skip uninit checks
# Use --fair-sched to improve threading simulation accuracy
valgrind --tool=helgrind --fair-sched=yes ./myprogram
# For large programs — limit stack trace depth (faster)
valgrind --num-callers=5 ./myprogram # Default is 12 frames
# Cache results between runs (Cachegrind only)
valgrind --tool=cachegrind --cache-sim=no ./myprogram # Branch onlyQuick Tool Reference
| Tool | Command | Purpose |
|---|---|---|
| Memcheck | --tool=memcheck (default) | Memory leaks, invalid access, uninit use |
| Callgrind | --tool=callgrind | CPU profiling, call graph |
| Helgrind | --tool=helgrind | Thread race conditions |
| DRD | --tool=drd | Thread errors (alternative to Helgrind) |
| Massif | --tool=massif | Heap memory profiling |
| Cachegrind | --tool=cachegrind | CPU cache & branch prediction |
| DHAT | --tool=dhat | Dynamic heap analysis |
| BBV | --tool=exp-bbv | Basic block vectors for simulation |
Quick Flags Reference
| Flag | Meaning |
|---|---|
--leak-check=full | Full leak report with stack traces |
--show-leak-kinds=all | Show all leak types |
--track-origins=yes | Show origin of uninitialized values |
--error-exitcode=N | Return exit code N if errors found |
--log-file=file.txt | Save output to file |
--suppressions=file | Load suppression rules |
--gen-suppressions=all | Generate suppression entries |
--trace-children=yes | Follow child processes |
--vgdb=yes | Enable GDB server |
--num-callers=N | Stack trace depth (default: 12) |
--error-limit=no | Report all errors (no cap) |
-v | Verbose output |
Exit Code Cheat Sheet
# Clean run (no errors): → returns your program's exit code
# Errors found + --error-exitcode=1 set: → returns 1
# Valgrind internal error: → returns 1
# Program killed by signal: → returns 128 + signal numberValgrind runs on Linux (best), macOS (limited), and not on Windows (use WSL or AddressSanitizer instead).
For Windows memory checking, consider: AddressSanitizer (-fsanitize=address) or Dr. Memory.