Valgrind Memory Tool

Valgrind cheatsheet — detect memory leaks and errors in C/C++. valgrind --leak-check=full, valgrind --track-origins=yes. Memcheck, Callgrind, Helgrind tools explained.

6 min read

What it is

Valgrind is a suite of debugging and profiling tools for dynamic analysis, most commonly used to detect memory management errors in programs written in C and C++.

Installation

Linux

sudo apt update && sudo apt install valgrind
# or
sudo yum install valgrind

macOS

Valgrind is not officially supported on macOS. However, you can install a port using Homebrew:

brew install valgrind

Note that the macOS port might have limitations or not be as robust as the Linux version.

Windows

Valgrind does not have direct Windows support. For Windows, consider using alternatives like Visual Studio’s built-in memory diagnostic tools or Dr. Memory.

Core Concepts

Memcheck

The primary tool in Valgrind for detecting memory errors. It works by running your program in a simulated environment and monitoring memory access.

Tool Options

Valgrind can run various tools, with memcheck being the default and most common. Other tools include callgrind (for call-graph profiling), cachegrind (for cache profiling), and helgrind/drd (for thread debugging).

Instrumentation

Valgrind instruments your executable at runtime, adding checks around memory operations. This instrumentation is what allows it to detect errors.

Commands / Usage

The general syntax is:

valgrind [valgrind-options] <your_program> [program_arguments]

Running Memcheck (Default Tool)

Basic Memory Error Detection

valgrind ./my_program arg1 arg2

Runs my_program with arguments arg1 and arg2, reporting memory errors detected by Memcheck.

Suppressing Errors

valgrind --suppressions=my_suppressions.supp ./my_program

Runs my_program and applies suppression rules defined in my_suppressions.supp to hide known or unfixable errors.

Tracking Leaks (Leak Check)

valgrind --leak-check=full ./my_program

Performs a thorough check for memory leaks, providing detailed information about where leaked memory was allocated. Other options: summary (default, less detail), no.

Tracking Uninitialized Memory Usage

valgrind --track-origins=yes ./my_program

Tracks the origin of uninitialized values, helping to identify where they were first introduced. This adds overhead but is crucial for debugging certain bugs.

Showing Call Stacks for Errors

valgrind --show-leak-kinds=all --verbose ./my_program

Provides more verbose output and shows all kinds of leaks (definite, indirect, possibly, reachable).

Profiling with Callgrind

valgrind --tool=callgrind ./my_program

Runs the program using the Callgrind tool, which generates call graph profiling information. This is useful for understanding program performance and function call overhead.

Profiling with Cachegrind

valgrind --tool=cachegrind ./my_program

Runs the program using the Cachegrind tool, which simulates CPU caches to profile cache misses and memory access patterns.

Thread Debugging with Helgrind

valgrind --tool=helgrind ./my_program

Runs the program using Helgrind to detect data races and other threading errors.

Thread Debugging with DRD

valgrind --tool=drd ./my_program

Runs the program using DRD, another tool for detecting data races and other threading errors, often with better performance than Helgrind.

Valgrind Options

--tool=<toolname>

Specifies which Valgrind tool to use. Defaults to memcheck.

valgrind --tool=callgrind ./my_program

--leak-check=<summary|yes|full|no>

Controls the level of detail for leak checking. full is most detailed.

valgrind --leak-check=full ./my_program

--show-leak-kinds=<all|definite|indirect|possible|reachable>

Determines which types of leaks to report. all is comprehensive.

valgrind --show-leak-kinds=all ./my_program

--track-origins=<yes|no>

Tracks the origin of uninitialized values. yes is more thorough but slower.

valgrind --track-origins=yes ./my_program

--gen-suppressions=<all|default|nofunc>

Generates suppression rules for detected errors. all attempts to suppress all errors found.

valgrind --gen-suppressions=all --leak-check=full ./my_program > leaks.supp

--suppressions=<filename>

Specifies a file containing suppression rules.

valgrind --suppressions=my_custom.supp ./my_program

--num-callers=<0-200>

The number of ancestor calls to show in stack traces. Higher values provide more context but increase overhead.

valgrind --num-callers=10 ./my_program

--log-file=<filename>

Writes Valgrind’s output to a file instead of stderr.

valgrind --log-file=valgrind_output.txt ./my_program

--quiet

Suppresses most Valgrind output, useful when redirecting to a file or piping.

--verbose

Increases the verbosity of Valgrind’s output.

--time-stamp=<yes|no>

Adds timestamps to the output.

--trace-children=<yes|no>

Tells Valgrind to also instrument child processes. Defaults to yes.

valgrind --trace-children=no ./my_program

--vgdb=<yes|no>

Enables the Valgrind GDB server. Defaults to yes.

valgrind --vgdb=yes ./my_program

--vgdb-error=<0-999>

Crash the client program when Valgrind encounters an error, and wait for the GDB server to be attached. 0 means always wait.

valgrind --vgdb-error=1 ./my_program

--vgdb-pipe=<filename>

Use a named pipe for communication with the GDB server.

Common Patterns

Detecting All Memory Errors and Leaks

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./my_program

This command is the workhorse for finding most memory-related bugs.

Generating Leak Suppressions

valgrind --leak-check=full --show-leak-kinds=all --gen-suppressions=all ./my_program > auto-suppressions.supp

Run this to generate a starting point for a suppression file. You’ll need to review and edit auto-suppressions.supp to keep only the necessary suppressions.

Debugging a Specific Allocation

If Valgrind reports a leak, and you want to see where it was allocated:

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

Look for the in 0x... lines in the output, which indicate the call stack where the memory was allocated.

Profiling with Callgrind and Visualizing Results

valgrind --tool=callgrind ./my_program
# Then visualize with kcachegrind or qcachegrind
kcachegrind callgrind.out.<pid>

This helps identify performance bottlenecks by showing function call counts and costs.

Debugging Child Processes

valgrind --trace-children=yes ./my_program_with_fork

Ensures that Valgrind instruments processes created by fork() or exec().

Attaching GDB to a Valgrind Session

valgrind --vgdb=yes --vgdb-error=1 ./my_program
# In another terminal:
gdb ./my_program
(gdb) target remote | vgdb --pid=<pid_of_my_program>

This allows you to debug your program under Valgrind’s supervision using GDB.

Gotchas

Performance Overhead

Valgrind significantly slows down program execution (often 10x-50x slower) and increases memory usage due to instrumentation. Be patient, especially with large programs or complex test cases.

False Positives/Negatives

While powerful, Valgrind is not infallible.

  • False Positives: Sometimes Valgrind might report an error that isn’t actually a bug (e.g., memory managed by a third-party library in an unusual way). Suppressions are used to handle these.
  • False Negatives: Valgrind might miss some subtle bugs, especially those related to complex race conditions or specific hardware behaviors.

Dynamic Libraries

Valgrind has varying levels of support for dynamically loaded libraries. It generally works well, but issues can arise with complex library loading scenarios.

Stripped Binaries

Valgrind relies on debug symbols (-g flag during compilation) to provide meaningful stack traces. If your binary is stripped, the error reports will be much harder to interpret. Always compile with debug symbols when using Valgrind.

macOS Port Limitations

The macOS port of Valgrind is less mature than the Linux version. Some features might not work as expected, and compatibility can be an issue.

__attribute__((noinline))

Functions marked with __attribute__((noinline)) might not appear correctly in Valgrind’s call stacks, especially with tools like Callgrind.

longjmp and setjmp

Valgrind has specific handling for setjmp/longjmp, but complex control flow involving these can sometimes confuse its analysis.

Exit Codes

Valgrind itself returns an exit code. 0 means no errors were detected by Memcheck. Non-zero values indicate errors or issues. Your program’s exit code is reported separately.