Skip to content
C++
Domain Deep-Dive
Advanced

MISRA-C++ Rules & Rationale

"MISRA-C++ 2023 mandatory and advisory rules for safety-critical C++: what they prohibit, why, and how to enforce them with clang-tidy and cppcheck."

TL;DR

MISRA-C++ is a coding standard for safety-critical C++ (automotive, aerospace, medical). MISRA-C++ 2023 covers C++17. Rules are mandatory (must follow), required (must follow unless formally deviated), or advisory (should follow). Enforce with clang-tidy's readability-* and cppcoreguidelines-* checks, or commercial tools like PC-lint, CodeSonar, or Klocwork.

Why MISRA exists

Certain C++ features, while valid, are too prone to misuse in safety-critical software:

  • Dynamic memory (new/delete) — heap fragmentation, non-deterministic timing
  • Exceptions — non-deterministic control flow, stack unwinding overhead
  • RTTI (dynamic_cast, typeid) — runtime overhead, code size
  • Recursion — unknown stack depth
  • Undefined behavior — implementation-defined behavior varies across compilers

MISRA replaces "it works on our compiler" with deterministic, verifiable behavior.

Key MISRA-C++ 2023 rules

Memory management

cpp
// MISRA Rule 21.6.1: No dynamic heap allocation on safety path
// BAD
void* buf = malloc(size);        // banned
std::string s = "hello";         // heap allocation
std::vector<int> v = {1, 2, 3}; // heap allocation

// OK — all memory allocated at startup, never freed
static std::array<uint8_t, 4096> rx_buffer;
static MyQueue<Event, 64> event_queue;

// Pattern: allocate all memory at init, use pool allocators
class SystemAllocator {
    static std::array<std::byte, 64 * 1024> pool_;
    static size_t offset_;
public:
    static void* alloc(size_t n) {
        // Only called during init phase
        void* p = pool_.data() + offset_;
        offset_ += (n + 7) & ~7u;
        return p;
    }
    // No free() — static lifetime
};

Exceptions

cpp
// MISRA Rule 15.0.1: Exception handling shall not be used
// Compile with: -fno-exceptions

// BAD
try {
    riskyOperation();
} catch (const std::exception& e) {
    // banned
}

// MISRA pattern — return-code based error handling
enum class Status { OK, ERROR_TIMEOUT, ERROR_INVALID };

Status readSensor(SensorId id, int32_t& out_value) {
    if (!validateId(id)) return Status::ERROR_INVALID;
    if (!waitReady(id)) return Status::ERROR_TIMEOUT;
    out_value = readRegister(id);
    return Status::OK;
}

// C++23: std::expected as MISRA-friendly error handling
#include <expected>
std::expected<int32_t, Status> readSensor(SensorId id);

Dynamic dispatch

cpp
// MISRA Rule 13.1.1: virtual functions allowed but with restrictions
// Virtual destructors required for polymorphic base classes

// MISRA-compliant virtual hierarchy
class Sensor {
public:
    virtual ~Sensor() = default;
    virtual Status read(int32_t& val) = 0;  // pure virtual allowed

protected:
    // No virtual in constructors/destructors of derived
};

// MISRA 13.1.3: dynamic_cast shall not be used
// BAD
Derived* d = dynamic_cast<Derived*>(base_ptr);

// OK — use visitor pattern or type tag instead
enum class SensorType { TEMPERATURE, PRESSURE };
class Sensor {
public:
    virtual SensorType type() const = 0;
};

Recursion

cpp
// MISRA Rule 17.2: No recursion (unbounded stack depth)
// BAD
int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);  // banned
}

// OK — iterative version
int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i)
        result *= i;
    return result;
}

// For tree traversal — use explicit stack with bounded depth
void traverseTree(Node* root) {
    static std::array<Node*, MAX_DEPTH> stack;
    int top = 0;
    stack[top++] = root;
    while (top > 0) {
        Node* node = stack[--top];
        process(node);
        if (node->right && top < MAX_DEPTH) stack[top++] = node->right;
        if (node->left  && top < MAX_DEPTH) stack[top++] = node->left;
    }
}

Type safety

cpp
// MISRA Rule 10.1: Implicit conversions that lose information are prohibited
// BAD
uint32_t u = 0xFFFFFFFF;
int32_t  s = u;               // narrowing — banned
float    f = 1234567890;      // precision loss — banned

// OK — explicit casts when you know what you're doing
int32_t  s = static_cast<int32_t>(u & 0x7FFFFFFF);

// MISRA Rule 5.0.1: Use typedef for fixed-width types
// BAD: int, long, char are implementation-defined width
int counter = 0;

// OK
#include <cstdint>
int32_t  counter = 0;
uint8_t  byte_val = 0;
uint16_t reg_val = 0;

Initialization

cpp
// MISRA Rule 9.1: All variables must be initialized before use
// BAD
int32_t result;
if (condition) result = compute();
use(result);  // UB if !condition

// OK
int32_t result = 0;
if (condition) result = compute();
use(result);

// Or better — structured initialization
int32_t result = condition ? compute() : 0;

Enforcement tools

clang-tidy MISRA-adjacent checks

bash
# Relevant clang-tidy checks (not full MISRA, but covers many rules)
clang-tidy src/*.cpp -- \
  -checks=\
cppcoreguidelines-*,\
readability-*,\
bugprone-*,\
performance-*,\
modernize-*

# Specific checks matching MISRA rules
# No dynamic memory: cppcoreguidelines-no-malloc
# Initialize variables: cppcoreguidelines-init-variables  
# Fixed-width types: cppcoreguidelines-avoid-magic-numbers

cppcheck

bash
# MISRA C 2012 check (use as proxy for C++ patterns)
cppcheck --addon=misra --suppressions-list=misra_suppressions.txt src/

# MISRA C++ 2008 (older standard, still useful)
cppcheck --enable=all --template='{file}:{line}: {id}: {message}' src/

Commercial tools

For DO-178C, ISO 26262, or IEC 61508 compliance, you typically need:

  • PC-lint Plus (Gimpel) — traditional MISRA enforcement
  • Polyspace Bug Finder (MathWorks) — formal verification + MISRA
  • Klocwork — MISRA + security
  • Axivion Bauhaus Suite — architecture + MISRA

MISRA deviation process

When a rule genuinely cannot be followed (e.g., using new in a memory-managed init phase), document a formal deviation:

cpp
// MISRA-C++ 2023 Rule 21.6.1 Deviation
// Deviation Ref: DEV-2024-001
// Justification: Dynamic allocation permitted during system initialization
//                phase only (before scheduler start). All allocations use
//                the SystemAllocator pool with bounded size.
// Reviewed by: Safety Manager (2024-01-15)
// Risk assessment: LOW — bounded allocation, no runtime deallocation
void* buf = SystemAllocator::alloc(sizeof(ControlBlock));  // NOLINT

Deviations must be reviewed and tracked in a deviation register.

MISRA vs AUTOSAR C++

AUTOSAR Adaptive Platform C++ Coding Guidelines build on MISRA-C++ 2008 but:

  • Allow C++14 features
  • Have additional rules for functional safety
  • Are required for AUTOSAR Adaptive implementations
  • Published as a separate document (AUTOSAR AP Guidelines for C++)

For new automotive projects, check which standard your OEM requires.

Edit on GitHubUpdated 2026-05-01T00:00:00.000Z