By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.
18px_cookie
e-remove
Blog

Why C Has Always Broken Static Analysis

C is the language SAST serves worst: the preprocessor, pointers, pattern rules, and no frameworks. Now AI agents are writing C faster than review can keep up.

Written by
Sarah Johnson
Sarah Johnson
Published on
August 19, 2026
Updated on
August 19, 2026

Static analysis has always struggled with C and C++, more than any other widely used language. Unfortunately, these are often the codebases that can least afford security flaws, including everything from flight software to pacemakers. Anywhere software needs direct hardware access or deterministic timing, C and C++ are still the default. These are also the systems where one memory-safety bug can become remote code execution, and where rewrites take decades.

Across Chrome, Android, iOS, Windows, and the Linux kernel, memory-safety bugs have made up 65 to 70 percent of high and critical vulnerabilities, year after year. Every one of those projects invested heavily in prevention, including static analysis. The scanners were there, but the vulnerabilities shipped anyway.

In 2022, the NSA went as far as recommending teams switch to memory-safe languages instead of C/C++. Bjarne Stroustrup, who created C++, pushed back. He's openly skeptical of the rewrite-it-in-Rust push, which is understandable considering rewriting billions of lines of working code won’t happen overnight. His counterproposal is that teams keep using C and C++ and make them safer with a combination of safer coding styles, support libraries, and enforcement via static analysis.

Static analysis, however, has historically struggled with C. In this blog post, we’ll go into the four main reasons.

A caveat before lumping the two languages together any further. Modern C++ gives you tools C doesn't: RAII, smart pointers, bounds-checked types like span. Code written in that style really does dodge a share of the lifetime and bounds bugs below, and C++ folks are right to point it out. But the analysis problems are shared (the preprocessor and pointer aliasing hit both, and templates arguably make things worse), most production C++ codebases carry decades of C-style code in their hottest paths. So, even though this post focuses on C, much of the following also applies to code written in C++ as well.

1. The analyzer never sees the code you wrote

Before a C file means anything, it goes through the preprocessor. Macros expand, #ifdef blocks appear and disappear, and every build configuration is effectively a different program. Most tools cope by hooking the build to learn what code even exists, which is why classic analyzers demand a working build and fail on anything you can't compile locally (e.g. a fork's PR or a vendored library).

2. Pointers defeat dataflow analysis

Finding bugs statically means tracking where data flows. In C, that means surviving pointer arithmetic, function pointers, void* casts, and manual memory management. Deciding whether two pointers can touch the same memory is a classically hard problem, and C forces it constantly. Tools either over-approximate (noise) or under-approximate (missed bugs). C SAST is famous for doing both.

3. C's bugs don't fit pattern rules

Modern SAST grew up on web vulnerabilities. SQL injection is a source-to-sink shape a rule engine expresses well. C's killers (buffer overflows, use-after-free, integer overflows that feed an allocation size) are about lengths, lifetimes, and arithmetic spanning functions and files. A rule can flag every strcpy. It can't tell you whether the length check three functions upstream actually bounds this copy, which is the question that matters.

4. There's no framework to model

Rule-based analysis scales on frameworks: model Spring once and you've covered most of the Java web ecosystem. C has no Spring, and C++ has no single one either (Qt, Boost, and a thousand in-house frameworks split the ecosystem). Every serious codebase carries its own allocators, string handling, and ownership conventions, so generic rules miss the bugs that matter in your code and flag ones that were never a problem.

Then the coding agents showed up

AI assistants aren't inventing new bug classes in C so much as amplifying the old ones. In a study of 8,918 C++ programs from three LLMs and human developers, AI-generated code triggered confirmed runtime violations at 3.6x the human rate (9.3% vs. 2.6%), and the bugs were the classics: signed integer overflow, heap and stack buffer overflows (CWE-190, -122, -121). Under static analysis, the AI code and the human code looked equally safe. The gap only surfaced under sanitizers and model checking, while a static-only CI gate passed both and failed silently.

That matches how these tools behave: agents imitate the patterns in front of them (Jason Turner has a nice demo). That's true in every language, and the inherited flaws differ by ecosystem. What's specific to C is the failure mode. The flaw an agent picks up from a twenty-year-old codebase is memory corruption, generated faster than anyone can review it. And nothing on by default catches it: the code compiles cleanly, and sanitizers (the closest thing C has to a runtime safety net) are opt-in and only flag bugs on paths your tests actually run.

Pick your poison

Existing C analyzers have different answers to these four constraints. Build-based analyzers (Coverity, Klocwork, CodeSonar) are accurate, but need the full build, plus the right compiler flags just to get the analyzer hooked in correctly (a project in itself), and typically run once per release, outside the developer workflow. Veracode pioneered a clever variation: analyze the compiled binary itself. That resolves the preprocessor ambiguity, since you're looking at what the compiler actually produced, though only for the configuration you built. And you still need a working build to produce the artifact, which puts the scan even further from the code being written. Build-less scanners get closer to developers, but lose accuracy and get noisy. And the generalist scanners, the multi-language tools that grew up on web stacks, do cover C and C++, but with a thin rule set next to their Java or JavaScript support, since pattern rules were always weakest here. So the teams that can least afford a missed bug choose between accuracy they see quarterly and workflow tools they've learned to ignore.

What’s next

The four problems above are decades old. What’s changed recently is the volume. A scanner that runs once a release was already a compromise; against agents generating memory-corruption bugs faster than anyone can review them, it's not a control at all. C needs analysis that reads source as written, reasons about how your code manages memory, and runs at the pace the code now arrives. That's the AI SAST scanner we've been building at Endor Labs, which now fully supports C code. See the release blog for more details.

Description goes here