Static analysis is the automated examination of source code (or compiled artifacts such as object files or bytecode) for defects, policy violations, and undefined behavior without executing the program. While compilers perform some static analysis-like reasoning and issue data-flow-based warnings, dedicated static analyzers typically apply deeper inter-procedural reasoning, more extensive data-flow tracking, and rule sets that go well beyond what a typical compiler warning covers.
In practice
In embedded development, static analysis is most commonly run on C and C++ source code using tools such as PC-lint/FlexeLint, Polyspace, Coverity, Cppcheck, Clang Static Analyzer, or PVS-Studio. These tools flag issues like uninitialized variables, null-pointer dereferences, integer overflow, out-of-bounds array accesses, unreachable code, and suspicious volatile access patterns -- defects that can be very hard to reproduce on target hardware because they depend on timing, compiler optimization level, or specific input sequences.
In safety-critical embedded work (IEC 61508, ISO 26262, DO-178C, IEC 62443), static analysis is often a mandatory or strongly recommended practice, though the specific requirement depends on the certification level, process, and project context. Many coding standards enforced through static analysis -- MISRA C, MISRA C++, CERT C, AUTOSAR C++14 -- exist specifically to eliminate constructs that are legal C/C++ but routinely lead to undefined behavior or portability problems. The blog post "Global Variables vs. Safe Software" illustrates the class of subtle, state-related bugs that static analysis and disciplined coding standards help surface.
A common pitfall is treating static analysis as a one-time gate rather than a continuous step in the build pipeline. False positives are real but manageable: most commercial tools allow per-finding suppressions with a documented rationale, which itself becomes an audit artifact. Tuning rule sets gradually -- enabling a broad baseline and adding stricter rules over time -- tends to work better than enabling everything at once and drowning the team in warnings.
Static analysis of source code is distinct from analysis of the linked binary or map file. Tools that analyze the ELF output (as discussed in "Analyzing the Linker Map file with a little help from the ELF and the DWARF") address different concerns such as symbol placement, section sizes, and stack usage estimation. Some advanced tools combine both phases -- for example, performing worst-case stack depth analysis on the call graph extracted from compiled object files rather than source alone.
Frequently asked
How is static analysis different from compiler warnings?
Compiler warnings are a form of static analysis, but they are constrained to what the compiler can observe cheaply during a single translation unit. Dedicated static analyzers perform inter-procedural and whole-program analysis: they track data flow across function and file boundaries, model library behavior, and apply hundreds or thousands of domain-specific rules. In practice, a well-tuned static analyzer finds real bugs that compile cleanly at -Wall -Wextra with no warnings.
Does static analysis replace unit testing?
No -- the two are complementary. Static analysis finds classes of bugs without requiring test inputs (uninitialized reads, type mismatches, unreachable code) but cannot verify runtime behavior against a specification. Unit tests execute code with specific inputs and check outputs, catching logical errors that analysis cannot reason about. The blog post "Short Circuit Execution vs. Unit Testing" touches on the limits of substituting one technique for another.
What is the difference between static analysis and dynamic analysis?
Static analysis examines code without running it. Dynamic analysis instruments and observes a running program -- examples include Valgrind, sanitizers (AddressSanitizer, UBSan), and runtime assertion checkers. Dynamic tools find only bugs exercised by the actual test inputs; static tools find bugs regardless of test coverage. On resource-constrained targets, dynamic instrumentation is often impractical, which makes static analysis especially valuable in embedded contexts.
What are MISRA rules, and how do they relate to static analysis?
MISRA C (and MISRA C++) is a set of coding guidelines originally developed for automotive safety software. The rules ban or restrict C/C++ constructs that are legal but dangerous -- implicit type conversions, unbounded recursion, certain pointer arithmetic patterns, and more. Static analyzers are the primary enforcement mechanism: MISRA compliance checking is built into tools such as PC-lint, Polyspace, Helix QAC, and Cppcheck. Compliance alone does not guarantee correct software, but it systematically eliminates a large class of undefined-behavior pitfalls.
Can static analysis handle RTOS-based code with multiple threads?
Some advanced static analyzers model concurrency and can detect data races, priority inversions, and improper use of synchronization primitives -- sometimes called "deep insight" or concurrency analysis. The blog post "Introduction to Deep Insight Analysis for RTOS Based Applications" covers this category of tooling. However, thorough concurrency analysis requires the tool to understand the RTOS API (task creation,
semaphore semantics, etc.), so results vary significantly by tool and RTOS.
Differentiators vs similar concepts
Static analysis is sometimes conflated with dynamic analysis (runtime instrumentation such as sanitizers or Valgrind) and with formal verification (exhaustive mathematical proof of program properties). In practice, static analysis occupies a space between compiler warnings and full formal methods: it is automated and scalable like a compiler but typically far more thorough, while stopping short of the exhaustive -- and expensive -- guarantees that formal verification provides (though the boundary is not strict, as some advanced static analysis tools incorporate formal-methods techniques). It is also distinct from binary/
ELF analysis tools that inspect the linked output for section sizes, symbol layout, or
stack depth estimates.