← All writing
12 August 2025·13 min

MISRA C++ Without the Misery: Writing Safety-Adjacent Code as a Student

What MISRA, AUTOSAR and DO-178C actually demand, the rules that matter, and how I refactored the CFAR pipeline to pass a clang-tidy MISRA profile without losing my mind.

If you want to write software for cars, planes, medical devices or defence systems, you eventually meet a coding standard with the words 'MISRA' or 'AUTOSAR' on the cover. I spent a summer reading them properly, then ran the CFAR radar pipeline through a clang-tidy MISRA profile to see what would happen. This is the field report.

What the standards actually are

MISRA C++:2008 was the original; MISRA C++:2023 is the modern rewrite, aligned to C++17. AUTOSAR C++14 (now folded into MISRA C++:2023) is the automotive variant. DO-178C is not a coding standard at all — it is the airworthiness process standard for software, and it requires you to pick a coding standard, not which one.

The common thread: define a safe subset of the language, ban the features that historically caused incidents, and require that every deviation be documented.

The rules that matter

There are around 170 rules in MISRA C++:2023. About a dozen change how you actually write code. The rest catch genuine bugs and you would want them anyway.

No implicit conversions between signed and unsigned. Every `size_t` to `int` is explicit.

No `goto`, no `setjmp`, no unions with non-trivial members.

No dynamic allocation after initialisation. All memory comes from pools sized at startup.

No exceptions in hard real-time paths. Error returns or `std::expected`-style sum types instead.

No recursion. The call stack must be statically boundable.

Every function has a single exit point, or the deviations are documented.

What broke in my code

The CFAR pipeline failed 41 MISRA rules on first pass. Twenty-eight of those were the same rule (implicit signed/unsigned conversion in for-loops). The rest split into three categories.

// before: implicit narrowing
for (uint32_t i = half; i < N - half; ++i) { ... }
int count = detections.size();   // size_t -> int

// after: explicit, MISRA-clean
for (std::size_t i = half; i < N - half; ++i) { ... }
const auto count = static_cast<std::int32_t>(detections.size());

Tedious, not hard. The second category was use of `auto` for non-deduced types where MISRA wants the type spelled out for reviewer clarity. The third was dynamic allocation: the FFTW3 plan creation calls `malloc` internally. The standard answer is to allocate the plan at startup and treat it as a constant-after-init resource, which I was already doing — but the rule requires a documented deviation noting that FFTW3 is a qualified third-party library.

Tooling

I used clang-tidy with the `cppcoreguidelines-*`, `bugprone-*`, `misc-*` and `readability-*` checks, plus the `cert-*` set, which together cover most of MISRA C++:2023 in spirit. There are paid MISRA checkers (Helix QAC, Coverity, PRQA) that catch the remaining ~15% with proper traceability, but for a student project the free toolchain gets you a long way.

clang-tidy --checks='-*,cppcoreguidelines-*,bugprone-*,misc-*,
  readability-*,cert-*,hicpp-*'
  -header-filter='.*' src/**/*.cpp -- -std=c++17

The cultural shift

The first week of writing MISRA-style code felt like typing with mittens on. By the third week it felt like writing English with a thesaurus open — more deliberate, slightly slower, much clearer. The diff at the end was about 8% larger; the bug surface, measured by warnings the unsafe build was emitting that the safe build was not, was 100% smaller.

The point of these standards is not to prevent clever code. It is to make sure that two years from now, when a reviewer who was not on the original team is looking at this function during an audit, they can read it linearly and decide whether it is correct without running it.

Where I deviated, and why

Rule 5-0-15 (no pointer arithmetic): deviation, FFTW3's API requires it. Documented.

Rule 6-6-5 (single exit point): I keep early returns for guard clauses; multiple exits at the top of a function are clearer than one at the bottom with a result variable threaded through five branches.

Rule 8-5-2 (no brace-initialisation with narrowing): deviation in two places where a literal is provably in-range. Comment explains.

What I would tell a first-year

Do not start a hobby project by trying to be MISRA-clean from line one. Write the thing, get it working, benchmark it, then run the checker and treat the warnings as a code review from a strict mentor. Most of the diff is mechanical. The handful of warnings that require real thought are almost always finding a genuine bug or a confusing piece of code. That is the actual product of compliance work, not a passing audit.

Safety standards are not about preventing clever engineers from writing clever code. They are about ensuring the next person to read it can be certain it does what it says.
C++17MISRASafety-CriticalDefence

Next essay

The Cache Line Is the Unit of Concurrency: A Visual Tour of False Sharing