Skip to main content

Command Palette

Search for a command to run...

How to Set Up Static Code Analysis in an Embedded C Project

Updated
โ€ข6 min read
How to Set Up Static Code Analysis in an Embedded C Project
S
Hi! I'm Sangeetha ๐Ÿ‘‹ โ€” embedded engineer, Yocto survivor, and firmware security nerd. This is where I turn a decade of hard-won embedded knowledge into posts you can actually use.

If you've ever shipped a firmware update only to discover a null pointer dereference hiding in production โ€” you'll know the pain I'm talking about.

Static code analysis is one of the most underused tools in embedded development. It costs nothing to set up, catches bugs before they reach hardware, and makes your codebase dramatically safer. Yet most embedded teams I've worked with either skip it entirely or bolt it on as an afterthought.

In this post I'll walk you through exactly how to set up static analysis in an Embedded C project โ€” from scratch, in under an hour.


What Is Static Code Analysis?

Static analysis tools examine your source code without executing it. They look for:

  • Null pointer dereferences

  • Memory leaks

  • Uninitialised variables

  • Buffer overflows

  • Unreachable code

  • Misuse of standard library functions

Think of it as a highly pedantic code reviewer that never gets tired, never misses a line, and works for free.


Why It Matters in Embedded C

In embedded systems, bugs don't just crash an app โ€” they can brick a device, create a security vulnerability, or cause a safety-critical failure in the field. Firmware is often running on hardware with no MMU, no OS protection, and no easy way to recover from a hard fault.

Static analysis gives you a safety net before the code ever touches hardware.

During my time working on security-critical STB platforms, static analysis caught entire classes of bugs that code review and unit tests missed โ€” especially around pointer arithmetic and integer overflow in protocol parsing code.


Tools You Need

For Embedded C projects, these are the two tools I recommend starting with:

1. Cppcheck

Free, open source, and excellent for C. Easy to integrate into any build system.

# Install on Ubuntu/Debian
sudo apt-get install cppcheck

# Install on macOS
brew install cppcheck

2. Clang-tidy

Part of the LLVM toolchain. More powerful, but requires a compilation database to work well with embedded projects.

# Install on Ubuntu/Debian
sudo apt-get install clang-tidy

We'll start with Cppcheck today โ€” it's the quickest win with zero configuration overhead.


Step 1: Run Cppcheck on Your Project

Navigate to your project root and run:

cppcheck --enable=all --inconclusive --std=c99 src/

What these flags do:

Flag Purpose
--enable=all Enables all checks (style, performance, portability, warnings)
--inconclusive Reports issues even when not 100% certain โ€” catches more bugs
--std=c99 Tells Cppcheck which C standard you're targeting
src/ Path to your source directory

You'll see output like this:

[src/parser.c:42]: (error) Null pointer dereference: ptr
[src/uart.c:78]: (warning) Uninitialised variable: rx_buffer
[src/config.c:15]: (style) Variable 'timeout' is assigned a value that is never used

Each line tells you: file, line number, severity, and description. Start with error severity first โ€” those are the critical ones.


Step 2: Suppress False Positives

Cppcheck sometimes flags code that is intentionally written a certain way โ€” especially in embedded C where you do things like cast integers to pointers for memory-mapped registers.

Suppress these cleanly using inline comments:

/* cppcheck-suppress invalidPointerCast */
volatile uint32_t *reg = (uint32_t *)0x40021000;

Or use a suppressions file for project-wide suppressions:

# suppressions.txt
invalidPointerCast
missingIncludeSystem
cppcheck --suppressions-list=suppressions.txt --enable=all src/

This keeps your output clean and focused on real issues.


Step 3: Integrate Into Your Makefile

Running Cppcheck manually is useful once. Running it automatically on every build is what actually changes your codebase quality.

Add this to your Makefile:

CPPCHECK = cppcheck
CPPCHECK_FLAGS = --enable=all --inconclusive --std=c99 --error-exitcode=1

.PHONY: static-analysis
static-analysis:
	\((CPPCHECK) \)(CPPCHECK_FLAGS) src/

# Add to your build target so it runs automatically
all: static-analysis build

The --error-exitcode=1 flag is important โ€” it makes Cppcheck return a non-zero exit code if errors are found, which means your build fails if static analysis fails. This enforces quality at the build stage.


Step 4: Add to Your CI Pipeline

If your team uses any CI/CD (Jenkins, GitHub Actions, GitLab CI), add the static analysis step there too. Here's a simple GitHub Actions example:

name: Static Analysis

on: [push, pull_request]

jobs:
  cppcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Cppcheck
        run: sudo apt-get install -y cppcheck
      - name: Run Cppcheck
        run: cppcheck --enable=all --std=c99 --error-exitcode=1 src/

Now every pull request gets automatically checked before it merges. No more "it looked fine in review" surprises.


Common Issues You'll Find in Embedded C

Here are the most frequent findings I've seen across embedded C codebases:

1. Uninitialised variables

uint8_t status;           // โŒ uninitialised
if (status == 0x01) { }  // undefined behaviour

uint8_t status = 0x00;   // โœ… always initialise

2. Integer overflow in calculations

uint8_t a = 200;
uint8_t b = 100;
uint16_t result = a + b;  // โŒ overflow happens before assignment

uint16_t result = (uint16_t)a + (uint16_t)b;  // โœ… cast first

3. Unchecked return values

read_sensor();            // โŒ ignoring return value

int ret = read_sensor();  // โœ… always check
if (ret != STATUS_OK) {
    handle_error(ret);
}

What to Do Next

Once Cppcheck is integrated and your obvious errors are fixed, the next step is to bring in clang-tidy for deeper analysis โ€” it can catch more subtle issues around type safety, API misuse, and modernisation opportunities.

I'll cover that in a future post.

For now, start with this:

  1. Install Cppcheck

  2. Run it on your project today

  3. Fix all error severity findings first

  4. Add it to your Makefile

  5. Commit the suppressions file to version control

Your future self โ€” and your team โ€” will thank you.


Resources

If you want to go deeper on writing safer embedded C, these books are worth keeping on your shelf:


Found this useful? Follow the blog for more practical embedded systems guides. Next up: Yocto for beginners โ€” what it is and why embedded teams use it.


โšก Affiliate Disclosure: Some links on this blog are affiliate links. If you buy through them, I may earn a small commission at no extra cost to you. I only recommend what I genuinely find useful.