5.5.19Embedded Systems & Real-Time Software

MISRA C — rules for safety-critical C code

3,374 words15 min readdifficulty · medium

MISRA C is a set of software development guidelines for the C programming language developed by the Motor Industry Software Reliability Association (MISRA). It aims to facilitate code safety, security, portability, and reliability in embedded systems, particularly in safety-critical applications like automotive, aerospace, and medical devices.

Version note: This note follows MISRA C:2012 (the current baseline, with later Amendments/Technical Corrigenda). Rule numbers differ from the older MISRA C:2004; where a rule changed between versions, this is called out explicitly.

Why MISRA C Exists

In a car's braking system or a medical device, a single bug can kill. MISRA C restricts the "dangerous" parts of C to create a safer subset that's more predictable and verifiable.

The 80/20 Insight

80% of safety-critical C bugs come from 20% of C's features: unchecked pointers, implicit conversions, dynamic memory, and implementation-defined behavior. MISRA eliminates or constrains these specific features.

Core Philosophy

Key Rule Categories

All rule numbers below are MISRA C:2012 unless stated otherwise.

1. Prohibited Language Features

Rule 21.3 (Required): The memory allocation and deallocation functions of <stdlib.h> shall not be used No: malloc(),calloc(),realloc(),free()\text{No: } \texttt{malloc()}, \texttt{calloc()}, \texttt{realloc()}, \texttt{free()}

Why? Dynamic allocation can:

  • Fail at runtime (out of memory)
  • Fragment memory unpredictably
  • Create timing non-determinism (heap management overhead)
  • Lead to memory leaks if cleanup fails

What to do instead? Use static allocation or stack allocation with compile-time known bounds:

// NO - MISRA violation
int *buffer = malloc(size * sizeof(int));
 
// YES - MISRA compliant
#define MAX_BUFFER 256
int buffer[MAX_BUFFER];

Rule 10.1 / 10.3 (Required): Operands / assignment across incompatible "essential types" are constrained

MISRA C:2012 uses an essential type model (an operand is essentially Boolean, character, signed, unsigned, enum, or floating). Rules 10.1 and 10.3 forbid mixing these in ways that silently lose data or change signedness.

Why? Implicit conversions can silently lose data or change signedness:

// NO - implicit int to unsigned conversion
unsigned int u = -1;  // Wraps to 0xFFFFFFFF!
 
// YES - explicit, reviewer sees intent
unsigned int u = 0xFFFFFFFFU;  // Value visible and intentional

2. Pointer Safety

Rule 18.1 (Required): A pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operand

Derivation from first principles:

  1. A pointer holds a memory address: p=addr(x)p = \text{addr}(x)
  2. Pointer arithmetic: p+np + n means addr(x)+n×sizeof(type)\text{addr}(x) + n \times \texttt{sizeof}(\text{type})
  3. This is only valid if addr(x)+n×sizeof(type)\text{addr}(x) + n \times \texttt{sizeof}(\text{type}) points within the same array or one past the end
  4. Problem: The compiler can't verify array bounds at compile time for arbitrary pointers
  5. MISRA solution: Only allow arithmetic on pointers that provably point into arrays

Why this step? Without this restriction, you can accidentally walk off the end of memory:

int x = 42;
int *p = &x;
p++;  // MISRA VIOLATION - p now points to undefined memory!

Rule 18.7 (Required): The address of an object with automatic storage shall not be copied to another object that persists after the first object ceases to exist

(Note: in MISRA C:2004 the closely related restriction was Rule 17.6.)

Why? Dangling pointer prevention:

int* get_local(void) {
    int local = 10;
    return &local;  // VIOLATION - local destroyed on return
}

3. Type Safety

Rule 10.1 (Required): Bitwise/shift operators shall not be applied to operands of essentially signed type

(In MISRA C:2004 the analogous rule was Rule 12.7.)

Derivation:

  1. Bitwise ops like &, |, ^, <<, >> treat values as bit patterns
  2. For signed integers, the bit pattern depends on two's complement representation
  3. Problem: Sign bit + right shift = implementation-defined (arithmetic vs logical shift)
  4. Example: (-1) >> 1 could be -1 or 0x7FFFFFFF depending on compiler

Solution: Only use bitwise ops on unsigned types where bit pattern is standardized:

// NO - MISRA violation
int mask = 0xFF;
int result = value & mask;  // signed operands
 
// YES - MISRA compliant  
unsigned int mask = 0xFFU;
unsigned int result = value & mask;

4. Control Flow Restrictions

Rule 15.6 (Required): The body of an iteration/selection statement shall be a compound statement (enclosed in braces)

Why? Prevents the "dangling else" bug and makes structure explicit:

// NO - MISRA violation
if (condition)
    action1();
    action2();  // Looks nested but ISN'T!
 
// YES - intent is clear
if (condition) {
    action1();
}
action2();  // Obviously not nested

Rule 15.5 (Advisory): A function should have a single point of exit at the end

Important version distinction: MISRA C:2004 made single-exit Required (old Rule 14.7, "A function shall have a single point of exit"). MISRA C:2012 relaxed this to an Advisory guideline (Rule 15.5) — multiple returns are permitted, but a single exit is still recommended because it simplifies cleanup and verification.

Derivation (why the advice still holds):

  1. Multiple returns create multiple exit paths
  2. Each exit path needs cleanup verification (unlock mutexes, close files)
  3. With nn return statements, you have up to nn places cleanup could be missed
  4. Single exit → cleanup happens in one place → easier to verify correctness
// Permitted in C:2012, but Advisory Rule 15.5 discourages early returns
int process(int x) {
    if (x < 0) return -1;
    if (x == 0) return 0;
    return x * 2;
}
 
// Single-exit style preferred by Rule 15.5 (Advisory)
int process(int x) {
    int result;
    if (x < 0) {
        result = -1;
    } else if (x == 0) {
        result = 0;
    } else {
        result = x * 2;
    }
    return result;
}

Rule Classification System

(MISRA C:2004 had only two categories: Required and Advisory. The Mandatory category is new to C:2012.)

Example: Complete MISRA-Compliant Function

Scenario: Read a temperature sensor and convert to Celsius, with bounds checking.

/* MISRA C:2012 compliant temperature conversion */
#include <stdint.h>
 
#define TEMP_MIN (-40)
#define TEMP_MAX (125)
#define ADC_MAX_VALUE (4095U)  /* 12-bit ADC */
 
/* Rule 8.2 (Required): function types shall be in prototype form
   Rule 8.7 (Advisory): objects/functions used only in one file
   should have internal linkage (static) */
static int16_t read_temperature(uint16_t adc_value);
 
static int16_t read_temperature(uint16_t adc_value) {
    int16_t temp;
    uint32_t scaled;  /* wider unsigned intermediate to avoid overflow */
 
    /* Rule 14.4 (Required): controlling expression of 'if' shall have
       essentially Boolean type — here a genuine comparison */
    if (adc_value > ADC_MAX_VALUE) {
        temp = TEMP_MAX;  /* Clamp to maximum */
    } else {
        /* Formula: temp_C = (adc / 4095) * 165 - 40
           Rearranged for integer math: (adc * 165) / 4095 - 40
           Rule 10.4 (Required): both operands of the same essential
           type category — promote to uint32_t before multiply */
        scaled = ((uint32_t)adc_value * 165U) / ADC_MAX_VALUE;
        /* Rule 10.3 (Required): explicit cast on assignment to a
           narrower/other essential type */
        temp = (int16_t)scaled + TEMP_MIN;
    }
 
    /* Single exit — Rule 15.5 (Advisory) satisfied */
    return temp;
}

Why each step?

  1. uint16_t adc_value: Fixed-width type (Rule 4.6 Advisory prefers <stdint.h> types) — portable across platforms
  2. uint32_t scaled: Prevent overflow in adc_value * 165 (would overflow 16-bit)
  3. Explicit casts: Make every essential-type conversion visible and intentional (Rules 10.3/10.4)
  4. Bounds check first: Safety before computation
  5. Single return: Satisfies Advisory Rule 15.5

Common Mistakes & How to Fix Them

Wrong approach:

uint16_t value = get_sensor();
int8_t small = (int8_t)value;  /* Data loss! value=300 → small=44 */

Why it feels right: The compiler stops complaining.

Why it's wrong: The cast silences the warning without fixing the problem. You're truncating 16 bits to 8 bits, losing data.

The fix:

uint16_t value = get_sensor();
if (value > 127U) {  /* Rule 14.4: explicit boolean test */
    /* Handle error - value too large */
    value = 127U;  /* Clamp or return error */
}
int8_t small = (int8_t)value;  /* Now safe */

Why it feels right: Writing extra checks and documentation takes time upfront.

Why it's wrong: The cost of bugs in production is orders of magnitude higher:

  • Automotive recall: $1M+ per incident
  • Medical device failure: Lawsuits, deaths, company shutdown
  • Aerospace bug: Mission failure, loss of spacecraft

The math: Cost=P(bug)×Impact×Units shipped\text{Cost} = P(\text{bug}) \times \text{Impact} \times \text{Units shipped} 0.01 \times \1M \times 10{,}000 = $100M$$

Even a 1% bug rate is catastrophic at scale. MISRA reduces P(bug)P(\text{bug}) by 10-100x.

Why it feels right: Fewer rules = less work, and Mandatory rules cannot be deviated.

Why it's wrong: Required rules make up the bulk of MISRA and capture decades of hard-won experience. Skipping them means relearning lessons through your own bugs — and deviating from a Required rule needs a formal documented justification, not silent omission.

Example: Rule 2.1 (Required) forbids unreachable code, and Rule 2.2 (Required) forbids dead code (code executed but with no effect). Ignoring these:

  • Confuses reviewers
  • May hide a real logic error (code dead by accident)
  • Wastes scarce program memory in embedded systems

(Steel-man / correction: earlier I mis-cited "dead code" as an advisory Rule 2.2 about headers — in fact Rule 2.2 (Required) is precisely about dead code, i.e. operations with no effect; unreachable code is Rule 2.1.)

The fix: Follow all rules by default, deviate only with documented reasoning.

Practical Verification Workflow

Source Code
    ↓
Static Analyzer (PC-lint Plus, Coverity, Polyspace, LDRA)
    ↓ (generates violation report)
Deviation Review (Safety team formally justifies each deviation)
    ↓
Code Review (Humans verify analyzer + logic)
    ↓
Unit Tests (Runtime verification)
    ↓
Integration Tests
    ↓
Certification (DO-178C, ISO 26262, IEC 62304)

Key insight: MISRA is necessary but not sufficient. You still need:

  1. Dynamic testing: Run the code under all conditions
  2. Formal methods: Prove properties mathematically (for critical sections)
  3. Code review: Human judgment for design issues (especially for undecidable rules)

Real-World Impact

Regulators require or strongly recommend MISRA C because it removes whole classes of defects before they can ship:

  • ISO 26262 (automotive) references coding standards such as MISRA C as a means of avoiding language-related faults.
  • IEC 62304 (medical device software) and DO-178C (airborne software) similarly expect a restricted, verifiable coding subset.

Illustrative categories MISRA C targets (the kind of defects that have caused field failures across the industry):

  • Unsafe pointer arithmetic / out-of-bounds access (Rule 18.1)
  • Dangling pointers to automatic objects (Rule 18.7)
  • Implicit sign/width conversions losing data (Rules 10.x)
  • Unbounded recursion risking stack overflow (Rule 17.2)

Accuracy caveat: Popular retellings sometimes attribute specific past automotive incidents to precise MISRA-rule violations. Public investigation findings are more nuanced and do not cleanly pin a single documented software defect to a single MISRA rule, so we present these as representative fault classes MISRA guards against, not as a claim about any one company's code.

Diagram: MISRA C Safety Mechanism

Figure — MISRA C — rules for safety-critical C code
Recall Explain to a 12-year-old

Imagine you're building a LEGO robot that has to work perfectly every time, because it's going to drive a real car or control a medical machine. C language is like having a giant bin of LEGO pieces, but some pieces are sharp, some break easily, and some don't quite fit together right.

MISRA C is like a rulebook that says: "Don't use the broken pieces. Don't use the sharp ones. Only connect pieces in ways we've tested a million times." It's not trying to be mean — it's trying to stop your robot from crashing the car or hurting someone!

The rules might seem annoying ("Why can't I use this cool pointer trick?"), but each rule exists because someone, somewhere, built a robot that failed catastrophically because they used that "cool trick." MISRA learned from those mistakes so you don't have to.

Connections

  • Real-Time Operating Systems (RTOS) — MISRA often combined with RTOS like FreeRTOS for scheduling
  • Static Analysis Tools — Tools like PC-lint Plus, Coverity, LDRA enforce MISRA
  • ISO 26262 — Automotive safety standard that recommends MISRA C compliance
  • DO-178C — Aerospace software standard (recommends MISRA or similar)
  • Memory Safety — MISRA's pointer rules directly address memory vulnerabilities
  • Formal Verification — MISRA-compliant code is easier to verify with tools like Frama-C
  • Embedded C Best Practices — MISRA is the gold standard for embedded guidelines
  • IEC 62304 — Medical device software standard that references MISRA

#flashcards/coding

What is MISRA C? :: A set of software development guidelines for C that restrict unsafe language features to improve safety, reliability, and verifiability in embedded and safety-critical systems.

Which MISRA C version does this note follow, and how does classification differ from 2004?
MISRA C:2012. It adds a Mandatory category (no deviation allowed) on top of the older Required and Advisory categories that existed in MISRA C:2004.
Why does MISRA C (Rule 21.3) prohibit dynamic memory allocation (malloc/free)?
Dynamic allocation can fail at runtime, fragment memory unpredictably, introduce timing non-determinism, and create memory leaks — all unacceptable in safety-critical real-time systems.
What are the three MISRA C:2012 rule categories?
Mandatory (must follow, no deviation), Required (follow, deviation allowed with formal documentation), and Advisory (recommended best practices).
Why does MISRA restrict pointer arithmetic to within the same array (Rule 18.1)?
To prevent walking off the end of allocated memory into undefined regions, which causes crashes and security vulnerabilities that can't be caught at compile time.
Why must bitwise operators not be applied to essentially signed types in MISRA?
Because bitwise/shift operations on signed integers involve the sign bit and produce implementation-defined behavior (e.g., arithmetic vs logical right shift), making code non-portable.
Is single-point-of-exit Mandatory in MISRA C:2012?
No. It was Required in MISRA C:2004 (old Rule 14.7) but is only Advisory (Rule 15.5) in MISRA C:2012 — multiple returns are permitted, single exit merely recommended.
Which rule prevents returning the address of an automatic (local) object in MISRA C:2012?
Rule 18.7 (Required) — the address of an automatic object must not persist after the object ceases to exist. (In C:2004 the related rule was 17.6.)
Why does MISRA require explicit type casts?
Implicit essential-type conversions can silently lose data, change signedness, or behave differently across compilers — explicit casts make the programmer's intent visible to reviewers and tools.
What problem does Rule 15.6 (braces on all control-statement bodies) prevent?
The "dangling else" bug and indentation-based confusion where code looks nested but isn't, leading to logic errors during maintenance.
What do MISRA C:2012 Rules 2.1 and 2.2 concern?
Rule 2.1 forbids unreachable code; Rule 2.2 forbids dead code (operations with no effect). Both are Required.
How does MISRA C relate to safety standards like ISO 26262?
ISO 26262 (automotive), IEC 62304 (medical), and DO-178C (aerospace) reference or recommend a restricted coding standard such as MISRA C as part of avoiding language-related faults.
Why is most of MISRA C called "decidable"?
Because most rules are designed to be automatically checkable by static analysis tools; a minority are classified as undecidable and require human judgement.
What is the 80/20 insight behind MISRA C?
80% of safety-critical C bugs come from 20% of C's features (pointers, implicit conversions, dynamic memory, undefined behavior), so MISRA restricts those specific features.

Concept Map

creates

include

include

include

constrains

defines

guided by

enables

ensures

Rule 21.3 bans

replaced by

Rule 10.x uses

achieves

C language power and risk

Dangerous features

Pointers and implicit conversions

Dynamic memory

Undefined behavior

MISRA C:2012

Safer C subset

Core principles

Static analysis checkable

Predictability across compilers

Static or stack allocation

Essential type model

Safety and reliability

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, MISRA C ek simple si baat par based hai — C language bahut powerful hai lekin utni hi khatarnaak bhi. Jaise pointer arithmetic, implicit type conversions, dynamic memory (malloc/free) — ye sab features aapko poora control dete hain, par ek chhoti si galti pura system crash kar sakti hai. Ab socho ki ye code kisi car ke braking system mein hai ya medical device mein — waha ek bug se kisi ki jaan ja sakti hai! Isliye MISRA C in "dangerous" features ko ban ya restrict kar deta hai, taaki ek safer subset of C bane jo zyada predictable aur verifiable ho.

Core intuition ye hai ki 80% safety-critical bugs sirf 20% features se aate hain — matlab unchecked pointers, implicit conversions, aur dynamic allocation jaisi cheezein. To MISRA bolta hai ki bhai in specific cheezon ko hi control kar lo, baaki C use karo normally. Jaise example mein dekha — malloc() use mat karo kyunki wo runtime pe fail ho sakta hai ya memory fragment kar sakta hai; iske jagah static allocation use karo jaha size compile-time pe hi fixed ho. Similarly unsigned int u = -1 likhoge to wo silently wrap hokar 0xFFFFFFFF ban jaayega — isliye explicit 0xFFFFFFFFU likho taaki intention clear rahe.

Ye topic isliye important hai kyunki jab aap embedded systems ya automotive/aerospace industry mein jaoge, waha code sirf "chalna" kaafi nahi hai — usko provably safe hona chahiye. MISRA rules mostly static analysis tools se automatically check ho jaate hain, isliye industry mein compliance ek must-have hai. Samajh lo ki ye ek discipline hai jo aapko sikhaati hai ki professional-grade reliable code kaise likha jaata hai, jaha predictability aur verifiability sabse upar hoti hai.

Go deeper — visual, from zero

Test yourself — Embedded Systems & Real-Time Software