Catching Double-Free Bugs with GCC's Built-in Static Analyzer
GCC's -fanalyzer catches double-free and use-after-free bugs that -Wall misses. A worked example shows how it traces feasible paths across branches, plus practical advice on CI integration, false-positive triage, and combining with AddressSanitizer.
01 Jul 2026, 18:56 UTC

The problem: memory bugs that slip past -Wall
You compile with -Wall -Wextra -pedantic and the build is clean. Yet the program crashes in production with a double-free or use-after-free that no warning caught. These bugs often live on error paths that are hard to exercise in tests, and they don't trigger obvious undefined behavior at compile time.
GCC 10 introduced a built-in static analyzer you enable with -fanalyzer. Unlike traditional warnings, it performs interprocedural, path-sensitive analysis: it tracks state like "this pointer was freed here" across function calls and reports a diagnostic that includes an event-by-event path showing exactly how the bug is reached.
How to use it on real C code
Add -fanalyzer to your compile command for the translation units you want to scan. A typical CI invocation looks like:
gcc -fanalyzer -c src/network.c -o /dev/null
The analyzer writes diagnostics to stderr. For machine-readable output that integrates with code-scanning dashboards, newer GCC releases support SARIF:
gcc -fanalyzer -fdiagnostics-format=sarif-file -c src/network.c
This produces network.c.sarif which tools like GitHub Code Scanning or GitLab SAST can ingest.
Worked example: a double-free on an error path
Consider this minimal program that allocates a buffer, frees it on success, but accidentally frees it again on an error path:
#include <stdlib.h>
#include <stdio.h>
int process_data(int flag) {
char *buf = malloc(64);
if (!buf) return -1;
if (flag) {
free(buf);
return 0;
}
/* error handling path */
fprintf(stderr, "processing failed\n");
free(buf); /* first free */
return -1;
}
int main(void) {
process_data(0); /* triggers error path */
return 0;
}
Compiling with gcc -Wall -Wextra -c demo.c produces no warnings. But gcc -fanalyzer -c demo.c emits a diagnostic similar to:
demo.c: In function 'process_data':
demo.c:18:5: warning: double-'free' of 'buf' [CWE-415]
18 | free(buf);
| ^~~~~~~~~
demo.c:13:9: note: first 'free' here
13 | free(buf);
| ^~~~~~~~~
demo.c:9:14: note: 'buf' allocated here
9 | char *buf = malloc(64);
| ^~~~~~~~~~~~~~
The analyzer traces the feasible path: allocation at line 9, first free at line 13 (when flag is true), then second free at line 18 when flag is false. It reasons about the control flow and state across the branch.
Trade-offs and limitations
- Compile-time cost: The analyzer explores execution paths, which can increase compile time significantly — often 2–10x on typical files. On heavily macro-generated code or very large translation units, state explosion can make it impractically slow.
- False positives: The analyzer may flag paths that are infeasible at runtime (e.g., guarded by assertions it doesn't understand). Teams need a triage or suppression workflow before gating CI on its output.
- Not a replacement for runtime tools: AddressSanitizer (
-fsanitize=address) and Valgrind catch bugs that require concrete execution, such as heap buffer overflows or uninitialized reads on specific inputs. Static and dynamic analysis complement each other. - Version differences: Options, diagnostic formats, and C++ support vary across GCC 10 through 13+. Check
gcc --help=common | grep analyzeror the installed man page for your exact version.
Practical adoption advice
- Start with a CI-only pass. Run
-fanalyzeron a subset of critical translation units in a nightly or PR job, not on every developer build. - Measure the cost. Compare compile time with and without
-fanalyzeron a representative file:time gcc -c file.cvstime gcc -fanalyzer -c file.c. - Triage before blocking. Collect SARIF output for a few runs, review findings, and establish suppression patterns (e.g.,
__attribute__((analyzer_noreturn))for known-safe functions) before making it a required check. - Cross-check with ASan. For any analyzer finding, build the same code with
-fsanitize=address -gand run the test suite. If ASan also triggers, you have high confidence; if not, investigate whether the static path is feasible.
GCC's analyzer is a pragmatic addition to a C project's quality gate: it finds real bugs that traditional warnings miss, with zero external dependencies. Treat it as a complementary signal — run it periodically, triage its output, and let it guide where to invest runtime testing effort.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.