Valgrind and Static Analysis
This chapter covers Valgrind (dynamic analysis without recompilation) and static analysis (finding bugs from source without running the program).
Why This Matters
Sanitizers require recompiling with instrumentation. Valgrind works on existing binaries, and static analyzers find bugs even before you run the code. Both are valuable complements to sanitizers.
Prerequisites
c.debug.1— debuggers.
Core Concept
Valgrind
Valgrind runs your program on a virtual CPU and tracks memory accesses. Its most-used tool is Memcheck, which detects:
- use of uninitialized memory;
- invalid reads/writes (heap overflow, use-after-free);
- memory leaks;
- mismatched free/delete.
valgrind --leak-check=full ./app
No recompilation needed (though -g gives better reports).
Static analysis
Static analyzers examine source code (or compiler IR) without running it. They catch:
- null dereferences;
- use-after-free;
- uninitialized reads;
- memory leaks;
- dead code and logic errors.
Tools: Clang Static Analyzer (scan-build / clang --analyze), GCC's -fanalyzer, cppcheck, and commercial tools.
Examples
Valgrind
gcc -g main.c -o app
valgrind --leak-check=full --track-origins=yes ./app
Clang static analyzer
scan-build make
# or
clang --analyze main.c
GCC -fanalyzer
gcc -fanalyzer main.c
How It Works
Valgrind interprets the machine code and shadows memory with definedness/ addressability metadata, checking every access. Static analyzers model program paths symbolically, looking for states that violate invariants (e.g., a pointer used after it was freed along some path).
Variations
Valgrind tools
Beyond Memcheck, Valgrind includes Cachegrind (cache profiling), Callgrind (call-graph profiling), Helgrind/DRD (race detection).
Compiler warnings as static analysis
-Wall -Wextra plus -Wconversion -Wshadow -Wstrict-aliasing are a first line of static analysis.
Common Mistakes
- Assuming Valgrind is as fast as native execution (it is 10–50x slower).
- Ignoring static-analysis warnings as noise (they often reveal real bugs).
- Not using
-gfor meaningful Valgrind reports.
Undefined Behavior
- Valgrind and static analyzers detect UB; they do not make it defined.
Portability
- Valgrind is primarily Linux (and some macOS). Static analyzers vary by
compiler.
Under the Hood
Valgrind is a dynamic binary translation engine with shadow-value tracking. Static analyzers perform path-sensitive analysis on the compiler's IR or on a custom AST.
Practical Usage
- Run Valgrind Memcheck on builds you cannot recompile with sanitizers.
- Run a static analyzer in CI as a fast first pass.
- Treat every report as a lead, and confirm before "fixing."
Exercises
1. Run Valgrind on a program with a leak and a use-after-free; read the reports. 2. Run clang --analyze or gcc -fanalyzer on a small buggy program. 3. Compare what Valgrind finds vs. what a sanitizer finds for the same bug.
Deep Challenge
Explain the difference between dynamic analysis (Valgrind) and static analysis, and give a bug each is uniquely good at finding. Then discuss false positives and soundness.
Related Concepts
c.debug.3— sanitizers.c.debug.1— debuggers.c.build.8— warning flags.
References
- Valgrind documentation, Clang Static Analyzer docs, GCC
-fanalyzerdocs.
Verification
- Valgrind and static analyzer behavior are documented tool features.
VERIFIED
- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Valgrind Memcheck
- [ ] Static analysis
- [ ] Compiler warnings as analysis
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.debug.valgrind | 0 | 6 |
| c.debug.static-analysis | 0 | 6 |