[GBGCPP] MISRA C++: Safer C++17 for critical systems
kurukii88
25 views
39 slides
Oct 23, 2025
Slide 1 of 39
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
About This Presentation
C++ is a powerful language, but its flexibility comes with risks, especially in safety-critical systems.
We will delve into the latest MISRA C++ rules, see practical code examples, and discuss how these guidelines can lead to safer systems.
MISRA C++:2023 defines a safer, more predictable subset of...
C++ is a powerful language, but its flexibility comes with risks, especially in safety-critical systems.
We will delve into the latest MISRA C++ rules, see practical code examples, and discuss how these guidelines can lead to safer systems.
MISRA C++:2023 defines a safer, more predictable subset of C++17, helping developers to avoid:
- Undefined behavior
- Relying on unspecified or implementation-defined behavior
- Potentially dangerous coding practices the language "allows"
- Subtle bugs stemming from common misunderstandings of the language
While some rules may seem restrictive or opinionated, C++ developers may benefit from keeping them in mind even for general-purpose C++.
Let's explore and reflect on these guidelines together and get some new ideas of how to write safer C++.
Size: 2.65 MB
Language: en
Added: Oct 23, 2025
Slides: 39 pages
Slide Content
MISRA C++:2023
Safer C++17 for critical systems
About me
Grew up in Rodos, Greece
Software Engineering at GU & Chalmers
Working with embedded systems
Teaching
DIT113, DAT265, Thesis supervision
C++, Coursera, Udemy
Open source projects
https://platis.solutions
https://github.com/platisd
Email: [email protected]
What is MISRA?
MISRA: Motor Industry Software Reliability Association
Originally: UK government initiative to develop guidelines for
embedded software in road vehicle electronic systems
Purpose: Promote best practices in safety- and security-related
electronic systems and other software-intensive applications
Focus: C and C++ for critical systems
Critical systems: Failure can lead to significant harm, loss of life,
injury, or substantial property damage
MISRA C++
MISRA C++: Guidelines for the use of C++ in critical systems
Latest Version: MISRA C++:2023, based on C++17, 179 guidelines
Vision: "Predictable subset", no undefined & unspecified behaviour
ISO26262: Functional safety standard for automotive systems
Merger: MISRA C++:2008AUTOSAR C++14MISRA C++:2023
Liability: Compliance does not guarantee legal immunity
Paywall: Copies of the guidelines available for 15£
Open source tooling: Impossible due to copyright
Free "copy": Mathworks offers a rather complete overview
Different behaviors in C++
Undefined (UB): Anything can happen, no point to reason about it
E.g. Dereferencing nullptr, accessing out-of-bounds memory
Implementation-defined: Implementation documents behavior
E.g. Exact size of int
Unspecified: Implementation doesn't need to document behavior
E.g. Order of evaluation of function arguments
Problems with C++ in critical systems
UB: Relying on undefined or unspecified behavior
Misuse: Misuse of features frequently allowed by the compiler
Misunderstanding: Areas of the language often misunderstood
E.g. Implicit conversions, memory management
Runtime checks: Unavailable (e.g. array bounds, pointer validity)
MISRA Guidelines
Directives: Guidelines harder to implement automated checks for
Rules: Guidelines with concrete description, easier to automate
Categories:
Mandatory: Must be followed, deviations not allowed
Required: Must be followed, deviations allowed
Advisory: Follow to a reasonable extent
Enforcement: Static analysis tools and code reviews
Formal Deviations
Achieving compliance with MISRA Coding Guidelines
Unavoidable: Must be documented and managed systematically
Deviation records
The violated guideline
The reason for the deviation
Background information
Risk assessment
Traceability: Which deviations are present in the codebase
E.g. Comments with machine-readable markers for deviations
Target audience
Developers, architects, testers, and managers
Working with C++ in critical systems
Interested in learning about coding practices in critical systems
Interested in improving code quality regardless of the project
Relying on undefined/unspecified behavior is generally bad
Less interesting for those who already work with AUTOSAR C++14
Will not focus on the differences, they are not that many anyway
Welcome to join the discussion and share experiences
Coming up
An overview of 25+1 interesting guidelines from MISRA C++:2023
Personal selection, not exhaustive, not in order of importance
Code examples to illustrate the guidelines
Reflections on the implications of adhering to MISRA C++:2023
Should you deviate or not?
"I don't need a babysitter!"
You may never need one, but...
Can you speak for your colleagues or future maintainers?
Critical systems need to prove safety
Proving safety is intricate, when subtle pitfalls can go unnoticed
Proving safety is hard, MISRA helps by limiting the language
Skepticism is healthy
Deviate when justified, but assess the risks carefully
Don't be negative, no large differences to modern C++
Rule x.x.x: Modern C++ practices (mostly) apply
MISRA C++:2023 C++ Core Guidelines
4.1.3: No occurrence of undefined or critical unspecified behaviour
0.1.2: The value returned by a function shall be used
0.2.2: A named function parameter shall be used at least once
6.4.1: A variable declared in an inner scope shall not hide a variable
declared in an outer scope
11.6.2: The value of an object must not be read before it's been set
15.1.3: Conversion operators and constructors that are callable
with a single argument shall be explicit
Rule 21.6.1: Dynamic memory should not be used
No STL class with std::allocator, e.g. std::vector, std::set
No function, e.g. new, delete, malloc, std::make_unique
Error-prone: Dynamic memory may lead to leaks or fragmentation
Out-of-memory: Catastrophic during operation of critical systems
Init-vs-run: Allocate memory in certain phases, e.g. initialization
Alternative: std::array, memory pools, allocators, static_vector
Advisory: Projects in certain domains might treat it as mandatory
int* p = new int(42); // Non-compliant
std::vector<int> v; // Non-compliant
auto f = std::make_shared<int>(42); // Non-compliant
Rule 21.6.2: Dynamic memory shall be managed
automatically
No new or delete in the code (except for placement new)
No malloc, free, realloc, std::unique_ptr::release() , etc.
Manual memory management is error-prone (leaks, double free)
Smart pointers: Avoid deviating from this guideline
Manual management: Minimize deviation with RAII wrappers
auto p = new int(42); // Non-compliant
delete p; // Non-compliant
auto f = std::make_unique<int>(42); // Compliant
f.release(); // Non-compliant
Rule 6.9.2: The names of the standard signed integer types
and standard unsigned integer types should not be used
Not allowed: int, short, unsigned long etc
Use fixed-width types instead: std::int32_t, std::uint16_t, etc
Why: The range of standard types is implementation-defined
Only the minimum range is guaranteed by the standard
May result in portability issues and wrong assumptions
Exception: Return type of main() and its argc argument
Exception: Creating type aliases, e.g. using ticks_t = long;
Advisory: Often enforced as it is easy to conform and check
Rule 8.2.2: C-style casts and functional notation casts shall
not be used
C-style casts: Almost limitless type conversions without checks
Misuse: Syntax can be confused with function or constructor calls
Exception: C-style casting to void is permitted
Alternatives: static_cast, dynamic_cast etc usually sufficient
auto i = (std::int16_t) 42; // Non-compliant
auto z = std::int16_t(42); // Compliant (not a cast, constructor call)
(void) i; // Compliant by exception
Rule 8.2.5: reinterpret_cast shall not be used
Undefined behavior: Casting between unrelated types
Almost equivalent to C-style casts which are not allowed
reinterpret_cast<T*>(ptr) to cast a pointer to T* if:
T is void, char, unsigned char, std::byte
reinterpret_cast<T>(ptr) to convert a pointer to an integer T if:
T is large enough to represent a std::uintptr_t
std::int32_t i = 42;
auto pI = reinterpret_cast<std::uint32_t>(i); // Non-compliant
std::int32_t val = 32;
auto pToVal = reinterpret_cast<std::byte*>(&val); // Compliant by exception
Rule 9.6.1: The goto statement should not be used
Why: Often leads to unmaintainable code, considered bad practice
Advisory: But often treated as mandatory
Required: goto-related rules, e.g. 9.6.3 allows only jumping to
labels declared later in the function body
std::int32_t i = 0;
loop_start:
if (i < 10) {
std::cout << i++ << std::endl;
goto loop_start; // Non-compliant for both 9.6.1 and 9.6.3
}
Rule 7.11.2: An array passed as a function argument shall not
decay to a pointer
Bounds: Arrays as arguments decay to pointers, size info lost
If code must work with different sizes, maintain dimensions
C-style arrays: Bad idea, 11.3.1 (advisory) forbids C-style arrays
Exception: Passing a string literal when expecting a const char*
String literal guaranteed to end with \0 so length is inferred
void processArray1(std::int32_t arr[42]); // Non-compliant, decays to pointer
void processArray2(std::int32_t* arr); // Non-compliant, decays to pointer
void processArray3(std::int32_t (&arr)[42]); // Compliant, enforces arr[42]
template<std::size_t N>
void processArray4(std::int32_t (&arr)[N]); // Compliant, array of N
Rule 24.5.2: The C++ Standard Library functions memcpy,
memmove and memcmp from <cstring> shall not be used
UB: If the source and destination overlap or not trivially copyable
Incompatible: memcmp may compare objects not logically equal
Floating point numbers, class objects due to padding and
alignment, buffers that may contain uninitialized data
struct Data {
std::byte first{};
// Content of potential padding is unspecified
std::uint64_t payload{};
};
if (memcmp(&data1, &data2, sizeof(Data)) == 0) { /* Non-compliant */ }
Rule 21.2.1: The library functions atof, atoi, atol and
atoll from <cstdlib> shall not be used
UB: If the string cannot be converted to the expected type
Modern alternatives: std::stoi, std::stod, std::from_chars
int i = atoi("42"); // Non-compliant
int j = std::stoi("42"); // Compliant
std::int32_t res = 0;
std::string_view str = "42";
auto [p, errc] = std::from_chars(str.data(), str.data() + str.size(), res);
if (errc == std::errc()) {
std::cout << "Parsed integer: " << res << '\n';
} else {
std::cerr << "Error parsing integer\n" ;
}
Rule 21.2.2: The string handling functions from <cstring>,
<cstdlib>, <cwchar> and <cinttypes> shall not be used
strcat, strcmp, strcpy, strlen, strtol etc
UB: Out of bounds access and null-termination issues
Modern & safer alternatives, e.g. std::string, std::string_view
void populate(char* message, std::size_t messageSize) {
const char* payload = "a message";
// Non-compliant calls to strlen and strcpy
if ((strlen(payload) + 1U) < messageSize) { strcpy(message, payload); }
}
std::string_view payload = "a message"; // (Modern) Alternative to char*
payload.size(); // Compliant alternative to strlen
payload.copy(message, payload.size()); // Compliant alternative to strcpy
Rule 21.2.3: The library function system from <cstdlib> shall
not be used
Security: Can execute arbitrary commands, might be exploited
Portability: Implementation-defined behavior may vary
Undefined behavior: Guidelines do not elaborate further
Alternatives: Use APIs, formally deviate, posix_spawn or similar
E.g. use std::filesystem for file operations
std::system("ls ."); // Non-compliant
for (const auto& entry: std::filesystem:: directory_iterator(".")) {
std::cout << entry. path() << std::endl; // Compliant
}
Rule 30.0.1: The C Library input/output functions shall not be
used
gets, fopen, fclose, fread, fflush, printf, scanf etc
Behavior: Undefined, unspecified and implementation-defined
Alternatives: std::cin, std::ifstream, std::ofstream etc
<fstream> compliant despite maybe using offending functions
printf("Hello, World!\n"); // Non-compliant
std::cout << "Hello, World!" << std::endl; // Compliant
Rule 21.10.1: The features of <cstdarg> shall not be used
va_list, va_start, va_end, va_arg etc
Why: Passing arguments via ... omits compile-time type checking
UB: va_end not called after va_start being used
UB: va_arg in multiple functions on the same va_list
Alternatives: Variadic templates, functions with fixed arguments
void doSomething(va_list args) { /* ... */ } // Non-compliant
template<typename T, typename... Args>
void doSomething(const T& first, const Args&... args) { /* ... */ } // OK
Rule 24.5.1: The character handling functions from <cctype>
and <cwctype> shall not be used
isalpha, isdigit, isspace, toupper, tolower etc
UB: When the character cannot be represented as unsigned char
Alternatives: Use equivalents from <locale>
Same as above but with an extra std::locale parameter
std::isalpha('a'); // Non-compliant
std::isdigit('1', std::locale{}); // Compliant
Rule 6.7.2: Global variables shall not be used
Non-constant variables in some global scope
Non-constant static member variables
UB: Due to data races when concurrent access is possible
Unspecified: Order of (dynamic) initialization not guaranteed
Unpredictable: Unexpected side effects, hard to track down
// Some global scope
std::int32_t currentSpeed{ getCurrentSpeed() }; // Non-compliant, non-const
const std::int32_t prevSpeed{ currentSpeed }; // Non-compliant, dynamic init
struct ComplexCar { ComplexCar(/* Some intricate initialization */); };
const ComplexCar car{}; // Non-compliant, dynamic initialization
Rule 6.7.1: Local variables shall not have static storage
duration
Temporal coupling: Data races that may lead to UB
Global state: Hard to understand, maintain and test
Destruction: Reverse order of creation, avoid dangling references
Deviations: Occasionally straightforward, but otherwise tricky
template<typename T>
T& getSingleton() {
static T instance{}; // Non-compliant, static storage duration
return instance;
}
Rule 19.0.2: Function-like macros shall not be defined
Why: Prefer functions instead
Safer: Type-checked, scope-aware, debuggable
Feature-rich: Overloading, templates, constexpr
Performant: Inlined by the compiler if needed
Exception: __LINE__, __FILE__, __func__ and the #, ## operators
#define SQUARE(x) ((x) * (x)) // Non-compliant
template<typename T>
T square(const T& x) { return x * x; } // OK, compliant alternative
#define TO_STRING(x) #x // Compliant by exception, against 19.3.1 (advisory)
Rule 19.6.1: The #pragma directive and the _Pragma operator
should not be used
Why: Non-portable, implementation-specific behavior
Advisory: Often treated as mandatory since it's easy to enforce
// Foo.h
#pragma once // Non-compliant
#ifndef FOO_H // Compliant, portable alternative
// ...
#endif
Rule 28.3.1: Predicates shall not have persistent side effects
Predicate: A function that (usually) returns a boolean value
Compare, Predicate etc in STL
Implementation-defined: Predicate may be copied
Unspecified: The order of predicate invocations
Unexpected: Side effects due to mutable internal state
using Samples = std::array< int32_t, 10U>;
int64_t countBadSamples(const Samples& s, int32_t& superBad) {
return std::count_if(s.begin(), s.end(), [&superBad](int32_t sample) {
if (sample < 100) { superBad++; } // Non-compliant
return sample < 0;
});
}
Rule 18.5.2: Program-terminating functions should not be
used
std::abort, std::exit, std::quick_exit, std::terminate etc
Why: Destructors not called, environment left in bad state
E.g. 3rd-party resources not released, files locked, etc
Exception: assert is OK since it is a debugging tool
Advisory: Often convenient to deviate if risks are understood
void handleError() {
std::cerr << "Fatal error occurred, terminating program\n" ;
std::exit(EXIT_FAILURE); // Non-compliant
}
Rule 18.3.1: There should be at least one exception handler
to catch all otherwise unhandled exceptions
Catch-all: catch(...) to handle all unhandled exceptions in main
Why: Program terminates in an implementation-defined manner
Advisory: Personally find it annoying, but easy to comply
int main() {
try { cool_company::Car car{}; } // Application code
catch (const cool_company::NoFuelException& e) { return 1; } // Specific
catch (...) { return 99; } // Catch-all for all other exceptions
return 0;
}
Rule 5.13.5: The lowercase form of L shall not be used as the
first character in a literal suffix
Why: l and 1 are visually similar, can lead to confusion
How: Use uppercase L for long literals, e.g. 42L
Literals: Unsigned literal numbers need suffixes (see 5.13.4)
int64_t i = 42l; // Non-compliant
int64_t j = 42L; // Compliant
int64_t k = 42ul; // Compliant because `l` not the first character
int64_t m = 42Ul; // Compliant but why not `UL`?
Rule 8.2.10: Functions shall not call themselves, either
directly or indirectly
Recursion: Not allowed, can lead to stack overflow
Why: Hard to predict worst-case operation count
Deviations: If you really must, document how risk is mitigated
Exception: Generally OK for constexpr ("core constant") functions
int32_t factorial(int32_t n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Non-compliant, recursion
}
Rule 9.4.1: All if ... else if constructs shall be terminated
with an else statement
Why: Defensive programming, ensures all cases handled
Empty else clause: May be used to indicate intentional handling
Default case: 9.4.2 requires a default case in switch statements
if (car.getSpeed() > 100) { /* OK */ }
else if (car.getSpeed() < 50) { /* There must be an else clause */ }
else { /* Compliant, handles all cases */ }
Rule 10.2.1: An enumeration shall be defined with an explicit
underlying type
Unscoped enums: The underlying type is implementation-defined
Scoped enums: The underlying type is int by default
Why: No implicit conversions, promotes predictability, portability
Exception: When all enumerators use default values
enum class Month { Jan = 1, Feb, Mar, Apr, May, Jun }; // Non-compliant
enum class Day : int8_t { Sun = 0, Mon, Tue, Wed, Thu, Fri }; // Compliant
enum class Color { R, G, B }; // Compliant by exception
Rule 14.1.1: Non-static data members should be either all
private or all public
Why: Member functions allow better access control
Robustness: Never accidentally break class invariants
Advisory: Common practice, but rather "too opinionated" as a rule
class Car {
public:
int32_t id{}; // Non-compliant, mixed access
int32_t getSpeed() const { return speed; }
private:
int32_t speed{};
};
Takeaways
MISRA rules define a "safer subset" of C++ for critical systems
BjarneStroustrup/profiles
C++ safety, in context
Avoids UB, unspecified or implementation-defined behavior
Avoids common pitfalls and bad practices
Overall a big improvement over MISRA C++:2008
Mostly in alignment with C++ Core Guidelines
Deviations possible, but must be documented and justified
General-purpose C++ projects may benefit from some rules