Neural Architecture Hub
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.0

Beginner

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.cpp

3. Basic Run (Memcheck — Default Tool)

# Simplest usage — Memcheck tool runs by default
valgrind ./myprogram

# With command-line arguments for your program
valgrind ./myprogram arg1 arg2

4. 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 check

6. 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 -g

7. 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 from

Intermediate

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 \\
         ./myprogram

10. 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 ./myprogram

11. 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 collecting

13. 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 ./myprogram

15. 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 ./myprogram

16. 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 misses

Advanced

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.c

18. 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 \\
         ./myprogram

20. 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) continue

21. 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 parsing

22. 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 errors

23. 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 ./myprogram

24. 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 only

Quick Tool Reference

ToolCommandPurpose
Memcheck--tool=memcheck (default)Memory leaks, invalid access, uninit use
Callgrind--tool=callgrindCPU profiling, call graph
Helgrind--tool=helgrindThread race conditions
DRD--tool=drdThread errors (alternative to Helgrind)
Massif--tool=massifHeap memory profiling
Cachegrind--tool=cachegrindCPU cache & branch prediction
DHAT--tool=dhatDynamic heap analysis
BBV--tool=exp-bbvBasic block vectors for simulation

Quick Flags Reference

FlagMeaning
--leak-check=fullFull leak report with stack traces
--show-leak-kinds=allShow all leak types
--track-origins=yesShow origin of uninitialized values
--error-exitcode=NReturn exit code N if errors found
--log-file=file.txtSave output to file
--suppressions=fileLoad suppression rules
--gen-suppressions=allGenerate suppression entries
--trace-children=yesFollow child processes
--vgdb=yesEnable GDB server
--num-callers=NStack trace depth (default: 12)
--error-limit=noReport all errors (no cap)
-vVerbose 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 number

Valgrind 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.

On this page