C++ Module 21 – Embedded C++
Safety-critical C++ and MISRA C++:2023
What coding guidelines for safety-critical C++ prevent, what MISRA C++:2023 covers and how it is licensed, and where analysers and C++26 checks fit in.
What you will learn
- Explain what coding guidelines for safety-critical systems try to prevent
- Describe what MISRA C++:2023 covers and how it is licensed
- Compare what compiler warnings, clang-tidy and the sanitizers each find in one function
- Record a deviation and review a module against the guideline categories
Before you start
What you’ll learn · Before you start
What you will learn
- Explain what coding guidelines for safety-critical systems try to prevent
- Describe what MISRA C++:2023 covers and how it is licensed
- Compare what compiler warnings, clang-tidy and the sanitizers each find in one function
- Record a deviation and review a module against the guideline categories
Before you start
On this page
A short function that averages sensor readings goes through the compiler’s warnings, clang-tidy and the sanitizers in this lesson, and three of its bugs still go unreported, among them a division by zero. Safety-critical C++ is code whose failure can injure people, in a car’s braking controller, an infusion pump or a railway signal, so it is written to coding guidelines that keep the language to a subset whose behaviour is defined, predictable and checkable by tools: a wrong value cannot be taken back once it has done harm.
MISRA C++:2023 is the guideline set written for this work: MISRA’s guidelines for the use of C++17 in critical systems. It is sold as a document whose text is protected by copyright, so this lesson explains what its rules aim at without quoting any. The C++ Core Guidelines, open to anyone online, cover much of the same ground for everyday code. Tools find many violations automatically; where a rule must be broken, a written A written record that lets a project break one guideline in stated places, saying why, what the risk is and which precautions keep it under control. records why.
Guidelines remove behaviour that no test can pin down
Coding guidelines for safety-critical C++ remove whatever makes a program’s behaviour hard to predict or to prove, from undefined behaviour: What a program does when it breaks a rule for which the C++ standard sets no requirements, such as reading past the end of an array. The compiler may assume it never happens, so the result can change with the compiler and the optimisation level. More in “Object lifetime and undefined behaviour in C++” to implementation-defined behaviour: Behaviour that the C++ standard leaves to each implementation, which must document its choice, such as sizeof(int). A program that relies on it is valid, but may give other results with another compiler or platform. More in “Object lifetime and undefined behaviour in C++”. The same concerns recur across guideline sets:
| Concern | What it covers, and why it goes |
|---|---|
| Undefined behaviour | Signed overflow, reading past an array, a dangling reference, an uninitialised read. The compiler may assume it never happens, so no test result can be trusted once it does |
| Behaviour the implementation chooses | Whether plain char is signed and how big long is, which each compiler documents; the order in which function arguments are evaluated, which it need not. Code that relies on one choice breaks on another compiler or processor |
| Conversions that change a value | Narrowing, sign changes, a floating-point value turned into an integer |
| Dynamic memory after start-up | An allocation can fail, fragment the heap or take unpredictable time |
| Unbounded recursion and stack use | The deepest stack must be provable before the device ships |
| Hidden control flow | Exceptions and similar mechanisms make the paths through a function harder to analyse, which is why many embedded projects build without exceptions |
Most of these were earlier lessons in this track; a guideline set turns their advice into rules a tool can check.
Checkpoint
Check: What safety guidelines remove
MISRA C++:2023 serves critical systems, the Core Guidelines everyday code
The two sets differ in who they are for and how a team gets them:
| Aspect | MISRA C++:2023 | The C++ Core Guidelines |
|---|---|---|
| Published by | MISRA, for C++17 | The Standard C++ Foundation, for current C++ |
| How to get it | Bought from MISRA as a PDF or a printed copy | Read online by anyone |
| Written for | Critical systems, with a defined compliance process | Everyday code |
| Checked by | Analysers, with deviations recorded | Compiler warnings, clang-tidy and code review |
MISRA C++:2023 targets C++17 as ISO/IEC 14882:2017 defines it, and MISRA sells it as a PDF from its webstore and as a printed copy. MISRA had announced that its new edition would integrate the AUTOSAR C++ guidelines, and a corrected edition, technical corrigendum 1, changes wording without adding guidelines. Compliance is defined by a companion document, MISRA Compliance, which says how deviations are recorded and what a claim of compliance must state. Anyone can read the C++ Core Guidelines online; their licence allows copying, use and changes for personal or internal business use, and many of their rules have matching clang-tidy checks.
A project applies its guideline set the same way on every change: a plan, checks, and every finding either fixed or covered by a deviation record, all summed up for each release:
How a project applies a guideline set, from the plan to the release
Text description of the diagram
The diagram is a flow from top to bottom.
- A guideline set: MISRA C++:2023, plus the project's own rules.
- The project's plan decides, for each guideline, whether it is mandatory, required, advisory or disapplied in this project.
- Checks run on every change: compiler warnings, analysers and the code review.
- Each check can produce a finding. Usually the code is fixed. When the rule cannot be met in that place, a deviation is recorded instead, drawn with a double border: why, the risk, the precautions and where it applies.
- Both paths end in a compliance summary for each release.
Checkpoint
Check: MISRA C++:2023 and the C++ Core Guidelines
Three checkers, three different slices of one function’s bugs
The draft averages a sensor’s last readings as a percentage of the maximum. All three checkers run on it:
// A first draft: average the last readings of a sensor and scale the result to a percentage of the sensor's maximum.
#include <cstdint>
#include <print>
int* history = nullptr; // a copy of the last readings, allocated on first use
std::uint8_t percent_of_max(const int* samples, int count, int max) {
if (history == nullptr) history = new int[count];
int sum = 0;
for (int i = 0; i <= count; ++i) { // one step too far
history[i] = samples[i];
sum += samples[i];
}
return sum * 100 / (count * max);
}
int main() {
const int readings[4] = {2'000'000'000, 2'000'000'000, 1, 1};
std::println("{}", percent_of_max(readings, 4, 4096));
} from session import run
# One draft function and three checkers, each with its full findings: the compiler's conversion warnings,
# clang-tidy (Homebrew's LLVM 23.1.2) with C++ Core Guidelines and bug-prone checks, and the sanitizers at run time.
run("""
clang++ -std=c++23 -Wall -Wextra -Wconversion -Wsign-conversion -fsyntax-only draft.cpp
clang-tidy --checks='-*,bugprone-*,cppcoreguidelines-*,misc-no-recursion,clang-analyzer-core.*' draft.cpp -- -std=c++23 -isysroot "$(xcrun --show-sdk-path)" 2>/dev/null | grep 'warning:'
clang++ -std=c++23 -g -fsanitize=address,undefined draft.cpp -o draft && ./draft 2>&1 | grep -E 'runtime error|SUMMARY'
""") Output
$ clang++ -std=c++23 -Wall -Wextra -Wconversion -Wsign-conversion -fsyntax-only draft.cpp
draft.cpp:8:47: warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
8 | if (history == nullptr) history = new int[count];
| ~~~ ^~~~~
draft.cpp:14:22: warning: implicit conversion loses integer precision: 'int' to 'std::uint8_t' (aka 'unsigned char') [-Wimplicit-int-conversion]
14 | return sum * 100 / (count * max);
| ~~~~~~ ~~~~~~~~~~^~~~~~~~~~~~~~~
2 warnings generated.
$ clang-tidy --checks='-*,bugprone-*,cppcoreguidelines-*,misc-no-recursion,clang-analyzer-core.*' draft.cpp -- -std=c++23 -isysroot "$(xcrun --show-sdk-path)" 2>/dev/null | grep 'warning:'
draft.cpp:5:6: warning: variable 'history' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]
draft.cpp:5:6: warning: variable 'history' provides global access to a non-const object; consider making the pointed-to data 'const' [cppcoreguidelines-avoid-non-const-global-variables]
draft.cpp:8:29: warning: assigning newly created 'gsl::owner<>' to non-owner 'int *' [cppcoreguidelines-owning-memory]
draft.cpp:11:9: warning: do not use pointer arithmetic [cppcoreguidelines-pro-bounds-pointer-arithmetic]
draft.cpp:11:22: warning: do not use pointer arithmetic [cppcoreguidelines-pro-bounds-pointer-arithmetic]
draft.cpp:12:16: warning: do not use pointer arithmetic [cppcoreguidelines-pro-bounds-pointer-arithmetic]
draft.cpp:14:18: warning: 100 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers]
draft.cpp:17:5: warning: an exception may be thrown in function 'main' which should not throw exceptions [bugprone-exception-escape]
draft.cpp:18:11: warning: do not declare C-style arrays, use 'std::array' instead [cppcoreguidelines-avoid-c-arrays]
draft.cpp:19:39: warning: do not implicitly decay an array into a pointer; consider using gsl::array_view or an explicit cast instead [cppcoreguidelines-pro-bounds-array-to-pointer-decay]
draft.cpp:19:52: warning: 4096 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers]
$ clang++ -std=c++23 -g -fsanitize=address,undefined draft.cpp -o draft && ./draft 2>&1 | grep -E 'runtime error|SUMMARY'
draft.cpp:12:13: runtime error: signed integer overflow: 2000000000 + 2000000000 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior draft.cpp:12:13
SUMMARY: AddressSanitizer: stack-buffer-overflow draft.cpp:11 in percent_of_max(int const*, int, int)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 checks_session.py
Step through the draft’s problems one at a time, each next to the checker that reported it:
The drawing shows the whole scene; its 9 steps are listed below.
Every step as text
- Line 8, the size. new int[count] turns the signed count into the unsigned type of an allocation's size. The compiler's -Wsign-conversion reports it.
- Line 14, the result. sum * 100 / (count * max) is an int returned as a std::uint8_t, which loses precision; -Wimplicit-int-conversion reports it.
- The owning pointer. history = new int[count] gives a new allocation to a plain pointer that owns it. clang-tidy's check cppcoreguidelines-owning-memory reports it.
- Ten more. clang-tidy also reports the global, twice, the pointer arithmetic three times, two magic numbers, an exception that may escape main, the C array and the array that decays to a pointer: 11 findings in all.
- Line 12, at run time. Only a run shows that two readings of 2,000,000,000 overflow an int. UndefinedBehaviorSanitizer reported the signed overflow at line 12.
- Line 11, at run time. AddressSanitizer reported the read one past the end of samples at line 11, as a stack-buffer-overflow.
- Never run. The program never calls the function with count 0, so the division by zero ran nowhere, and neither the compiler nor clang-tidy proved the case.
- Two more. The memory that is never freed, and the history that keeps the size of the first call so that a later, longer call writes past it, were reported by no checker either.
- Slices. Each checker saw its own slice of the problems, and three were in no slice at all. A rule such as no dynamic memory after start-up removes a whole class of them instead.
The three checkers found different problems. The compiler’s conversion warnings flagged two implicit conversions,
one into the size of the allocation and one that narrows the result to std::uint8_t. clang-tidy flagged the heap
allocation owned by a plain pointer, the global, the pointer arithmetic and the C array. Only the sanitizers, while
the program ran, found the signed overflow at line 12 and the read past the end at line 11. None of them reported
the division by zero when count is 0, the memory that is never freed, or that history keeps the size of the first
call, so a later, longer call writes past it. Guidelines exist because each tool sees only a slice: a rule such as
“no dynamic memory after start-up” removes a whole class of bugs that no tool reliably finds.
Key idea
Each checker sees only a slice of a function’s problems; a guideline that removes a whole class of constructs covers what none of them reaches.
Checkpoint
Check: What each checker can find
Practise
Practice that adapts to you
Practise this lesson
10 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Rewriting the function step by step, with the checker output at each step Pro: opens with a Pro, Premium or Ultimate pass
The draft is rewritten here in three steps, one class of problem at a time, and the same three checkers run after each step. The output stays the same, 48 per cent, while the findings fall to zero.
In this part:
- Step 1: removing the undefined behaviour
- Step 2: making every conversion explicit and proved
- Step 3: no heap, no globals, no pointer arithmetic
- The findings at each step, measured
- Recording a deviation, and auditing suppressions
1 diagram · 7 runnable examples (C++, Python, Shell) · about 7 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Categories, analysers, C++26 checks, and a module to review Pro: opens with a Pro, Premium or Ultimate pass
A guideline set becomes a process once a project decides which rules may bend, knows what its tools can prove, and uses what the language now checks for it. This part covers all three, then hands you a module to review.
In this part:
- Mandatory, required, advisory: deciding what may be broken
- Decidable and undecidable rules, and what an analyser’s silence means
- What C++26 adds: contracts and a hardened library, run
- Your turn: review a module against the checklist
- The model review, issue by issue
4 runnable examples (C++ (contract.cpp), C++ (hardened.cpp), C++ (valve.cpp), C++ (valve_test.cpp), Python) · about 9 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
-
Fresher to mid · Warm-up
What does a coding guideline set such as MISRA C++:2023 try to prevent, and why are tests alone not enough?
It restricts C++ to a subset whose behaviour is defined, predictable and checkable: no undefined behaviour such as signed overflow or reading past an array, no reliance on choices the implementation makes, such as the signedness of plain char, no conversions that silently change a value, no dynamic memory after start-up, no unbounded recursion, and control flow simple enough to analyse. Tests alone are not enough because they show behaviour only for the inputs they try, and undefined behaviour can pass every test and still fail with another compiler, optimisation level or input. Guidelines remove whole classes of bugs instead of finding them one at a time, analysers check many of the rules on every change, and a written deviation covers each place where a rule cannot be met.
-
Senior to staff
Your product must follow MISRA C++:2023, but a third-party communication library you need uses exceptions, dynamic memory and recursion. What do you do?
-
Senior to staff
An analyser reports 3,000 guideline findings on an existing 80,000-line code base that must now comply. How do you plan the work?
-
Mid to senior
Does adopting C++26 contracts and a hardened standard library make a guideline set such as MISRA C++:2023 unnecessary?
Key takeaways
- Safety-critical C++ restricts the language to behaviour that is defined, predictable and checkable.
- MISRA C++:2023 covers C++17 for critical systems; it is bought from MISRA, and its text is copyrighted.
- Anyone can read the C++ Core Guidelines online, and many of their rules have clang-tidy checks.
- Compiler warnings, analysers and sanitizers each find a different slice; guidelines remove whole classes of bugs.
- Where a rule cannot be met, a deviation record states where, why and with which precautions.
References
- MISRA C++:2023 released, including hardcopy (MISRA)
- MISRA (home page and news, including the integration of the AUTOSAR C++ guidelines) (MISRA)
- MISRA C++:2023 technical corrigendum update (MISRA)
- MISRA Compliance (MISRA)
- MISRA Compliance:2020: Achieving compliance with MISRA Coding Guidelines (HORIBA MIRA Limited for the MISRA Consortium)
- C++ Core Guidelines (Standard C++ Foundation)
- C++ Core Guidelines licence (Standard C++ Foundation)
- P2900R14: Contracts for C++ (ISO/IEC JTC1/SC22/WG21)
- P3471R4: Standard library hardening (ISO/IEC JTC1/SC22/WG21)
- Working Draft, Programming Languages: C++ (N5050, the C++26 final draft) (ISO/IEC JTC1/SC22/WG21)
- Hardening Modes (libc++) (LLVM Project)
- Clang-Tidy Checks (LLVM Project)
- UndefinedBehaviorSanitizer (LLVM Project)
- AddressSanitizer (LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress