5.5.19 · D5Embedded Systems & Real-Time Software

Question bank — MISRA C — rules for safety-critical C code

2,710 words12 min readBack to topic

This page is a self-test. Each line hides its answer after the ::: — try to answer out loud before revealing. The goal is not to memorise rule numbers but to catch the misconceptions MISRA is built to prevent. If any term feels unfamiliar, walk back to the parent topic first.


Reference card — read this before you start

Before you can answer the traps, you need three vocabularies pinned down: the rule categories (how strict a rule is), the essential type model (how MISRA classifies values), and a one-line reminder of every rule cited on this page. Keep these open.

Figure — MISRA C — rules for safety-critical C code
Figure — MISRA C — rules for safety-critical C code
Recall One-line reminder of every rule cited on this page

Rule 10.1 (Required) ::: Bitwise / shift operators shall not be applied to operands of essentially signed type. Rule 10.3 (Required) ::: A value shall not be assigned to an object of a narrower or different essential type category. Rule 14.4 (Required) ::: The controlling expression of an if / while shall have essentially Boolean type. Rule 15.5 (Advisory) ::: A function should have a single point of exit at the end. Rule 15.6 (Required) ::: The body of an iteration/selection statement shall be a compound statement (in braces {}). Rule 18.1 (Required) ::: A pointer from arithmetic shall address an element of the same array as the original pointer. Rule 18.7 (Required) ::: The address of an automatic (local) object shall not outlive that object. Rule 21.3 (Required) ::: The memory allocation functions of <stdlib.h> (malloc/calloc/realloc/free) shall not be used.


Picturing the three worst traps

The pointer, bitwise, and shift traps are all invisible in the source — they only bite at the bit/memory level. These figures make that level visible so the reveals below have something to point at.

Pointer arithmetic: valid vs invalid increments

Figure — MISRA C — rules for safety-critical C code

Look at the figure. The green arrows stay inside the array a[4] or land exactly on the one-past-the-end slot — all permitted by Rule 18.1. The red arrow starts from &x, a lone int that is not an array; incrementing past it lands in undefined memory. That is the exact trap in the "Spot the error" section.

Signed vs unsigned bit patterns, and the mixing trap

Figure — MISRA C — rules for safety-critical C code

The top row shows the same bit pattern read two ways: as a signed int16_t (sign bit in orange makes it negative) and as an unsigned uint16_t (all bits count as magnitude). The bottom row shows the Rule 10.3 mixing trap: a negative int16_t widened into a uint32_t sign-extends into a huge positive number — silent, and exactly why MISRA keeps bit-level work in the unsigned bucket.

Why (-1) >> 1 is implementation-defined

Figure — MISRA C — rules for safety-critical C code

Right-shifting a signed value has two legal implementations. The figure shows both: an arithmetic shift copies the sign bit inward (result stays -1), while a logical shift feeds in a 0 (result becomes 0x7FFF...). Because the C standard lets the compiler pick either, portable code cannot use signed right shift — that is what Rule 10.1 removes.


True or false — justify

MISRA C bans all use of pointers in safety-critical code.
False. MISRA constrains dangerous pointer uses (unbounded arithmetic, dangling addresses) but pointers are essential in C and freely allowed when their target is provably valid — e.g. passing a pointer to a fixed array is fine.
MISRA C:2012 forbids a function from having more than one return statement.
False. Single-exit is only Advisory (Rule 15.5) in C:2012; multiple returns are permitted. It was Required in C:2004 (Rule 14.7), which is the source of this common misconception.
A "Required" rule can never be violated in compliant code.
False. A Required rule may be deviated from with a formal, documented deviation. Only Mandatory rules (new in C:2012 Amendment 1) permit no deviation at all.
Following MISRA C guarantees your program is free of bugs.
False. MISRA removes whole classes of undefined/risky behaviour, making code more predictable and verifiable — but logic errors, wrong requirements, and hardware faults remain entirely possible.
MISRA C is a compiler that checks your code.
False. MISRA C is a set of guidelines (a document). Checking is done by separate Static Analysis Tools that implement the rules; MISRA itself compiles nothing.
Because bitwise operators are banned on signed types, MISRA effectively bans bit manipulation.
False. Bit manipulation is allowed — just on essentially unsigned operands (see the essential-type figure), where the bit pattern is standardised. Rule 10.1 pushes you to use unsigned types, not to abandon masks and shifts.
Rule 21.3 (no malloc/free) exists because dynamic memory is slow.
Mostly false — speed is minor. The real reasons are non-determinism: allocation can fail at runtime, fragment the heap unpredictably, and introduce variable timing, all of which are unacceptable in a hard real-time or life-critical path.
A rule number is the same across MISRA C:2004 and C:2012.
False. Numbering was reorganised. E.g. single-exit moved from C:2004 Rule 14.7 to C:2012 Rule 15.5, and signed-shift moved from C:2004 Rule 12.7 to C:2012 Rule 10.1.
Static allocation is always safer than stack allocation under MISRA.
Not necessarily. Both avoid the heap and are acceptable; the requirement is compile-time-known bounds, not that one storage class beats the other. A huge static array can waste RAM just as a deep recursion can overflow the stack.
An Advisory rule can be ignored silently with no record.
Not ideally. Advisory means "follow where reasonable", but departures ought still to be recorded — only the strictness of the review differs from a Required deviation, not the habit of documenting.

Spot the error

int *p = &x; p++; where x is a single int — what breaks?
p++ now addresses memory past a lone object (Rule 18.1 — arithmetic must stay within the same array). Since x is not an array (see the red arrow in the pointer figure), p points to undefined memory.
unsigned int u = -1; — why is this a MISRA violation even though it "works"?
It relies on an implicit signed-to-unsigned conversion (Rule 10.3) that silently wraps to 0xFFFFFFFF. A reviewer cannot see the intended value; MISRA wants the intent explicit, e.g. 0xFFFFFFFFU.
if (cond)
    action1();
    action2();   /* trap: NOT inside the if */
What did the author think happens, versus what actually happens?
They think both calls are inside the if. In reality only action1() is guarded; action2() always runs. Rule 15.6 requires braces so the block is unambiguous — see the corrected form below.
The braced fix for the above — how does Rule 15.6 remove the ambiguity?
Wrapping the intended body in {} makes the boundary explicit, so a stray second statement can no longer look nested:
if (cond) {
    action1();
}
action2();       /* obviously always runs */
int* get_local(void){ int local=10; return &local; } — what is wrong?
local has automatic storage and is destroyed when the function returns, so the returned address is a dangling pointer (Rule 18.7). Dereferencing it later is undefined behaviour.
int mask = 0xFF; int result = value & mask; — which rule and why?
Rule 10.1 — bitwise & is applied to essentially signed operands. On signed types the sign bit makes some bit-level operations implementation-defined, so both operands should be unsigned.
(-1) >> 1 — what makes this dangerous?
The result is implementation-defined: the compiler may do an arithmetic shift (giving -1) or a logical shift (giving 0x7FFFFFFF) — the two branches in the shift figure. Portable code cannot rely on either, so shifting signed values is disallowed by Rule 10.1.
A reviewer says "this function is compliant, it has one return and no malloc." Is that enough to conclude compliance?
No. Compliance means satisfying all applicable rules (type model, pointer rules, control flow, prototypes, linkage, etc.), each verified — spotting two green rules proves nothing about the rest.

Refactor walkthrough — faulty function to compliant

Read each numbered stage below as one MISRA check applied in sequence, turning the broken version into the compliant one.

Faulty starting point — how many distinct violations can you find?

int* scale(int adc) {
    int *buf = malloc(4);         /* A */
    if (adc)                      /* B */
        buf[0] = adc & 0xFF;      /* C */
    return buf;                   /* D */
}

::: Four: (A) malloc breaks Rule 21.3; (B) the if controlling expression is int, not Boolean (Rule 14.4), and its body lacks braces (Rule 15.6); (C) bitwise & on signed operands (Rule 10.1); (D) returning heap memory the caller must free — the whole dynamic-memory pattern Rule 21.3 forbids.

Stage 1 — fix Rule 21.3: what replaces malloc?
Use fixed, compile-time-bounded storage passed in by the caller, so no heap and no ownership question: void scale(uint16_t adc, uint16_t *buf).
Stage 2 — fix Rules 14.4 and 10.1: how should the condition and the mask be written?
Make the condition a genuine Boolean comparison and keep the mask in the unsigned bucket: if (adc > 0U) and buf[0] = adc & 0xFFU;.
Stage 3 — fix Rule 15.6: what wraps the body?
Braces around the if body, giving the final compliant form:
static void scale(uint16_t adc, uint16_t *buf) {
    if (adc > 0U) {
        buf[0] = adc & 0xFFU;
    }
}

Why questions

Why does MISRA prefer explicit constants like 0xFFFFFFFFU over -1 for an unsigned target?
Because it makes the programmer's intent visible in the source. An explicit value survives review and is portable, whereas an implicit conversion hides both the true value and the fact that a conversion occurred.
Why is single-exit still recommended even though C:2012 downgraded it to Advisory?
With n return statements there are up to n places where cleanup (unlock mutexes, close files, free resources) could be forgotten. One exit point concentrates cleanup in one verifiable place, easing review and Formal Verification.
Why does MISRA lean so heavily on rules that a tool can check automatically?
Manual review is slow and error-prone at scale. Decidable rules let Static Analysis Tools enforce them consistently across an entire codebase, so human effort focuses on the few undecidable rules needing judgement.
Why introduce the essential type model instead of just using C's built-in types?
C's usual arithmetic conversions silently promote and mix types (signed/unsigned/char/float), losing data or flipping signedness — the sign-extension trap in the bit-pattern figure. The essential type model gives MISRA a precise vocabulary to forbid the risky mixes while allowing safe ones.
Why is MISRA especially relevant to standards like ISO 26262, DO-178C, and IEC 62304?
Those functional-safety standards demand evidence of predictable, verifiable code. A MISRA-compliant C subset provides a recognised, tool-checkable coding standard that helps produce that evidence.
Why does banning the heap improve real-time behaviour on an RTOS?
Heap allocation has variable, hard-to-bound execution time and can fail unpredictably. Removing it makes worst-case execution time analysable and memory usage fixed — both prerequisites for meeting deadlines.
Why does the Mandatory category exist at all if Required already needs sign-off to break?
Because some rules guard against errors so severe (or so clearly always-wrong) that no justification could make a violation safe — so MISRA removes the deviation option entirely.

Edge cases

Is p + 1 where p points to the last element of an array a violation of Rule 18.1?
No. C explicitly permits a one-past-the-end pointer to exist (it just must not be dereferenced) — the rightmost green arrow in the pointer figure. Rule 18.1 allows arithmetic landing within the array or exactly one past its end.
What about arithmetic on a pointer to a single scalar that is not an array element?
Treated as an array of length one for arithmetic. So &x + 1 (one-past) is technically valid, but &x + 2 or dereferencing &x + 1 is undefined — the safe rule of thumb is: don't do arithmetic on pointers to lone objects.
An empty if body written as if (cond); — does Rule 15.6 still apply, and is this even sensible?
Yes it applies — the controlling statement is the empty ;, itself a trap (the if does nothing). Rule 15.6 pushes you to write if (cond) { } so an intentionally empty body is explicit and an accidental stray ; becomes visible.
Zero-length or degenerate loops like for(;;) — are these forbidden, and how should an intentional infinite loop be written?
A deliberate infinite loop is fine (RTOS tasks use them), but Rule 15.6 still requires a braced compound body, and the intent should be unmistakable. Prefer for (;;) { /* task body */ } over while (1), and give the empty case an explicit body:
for (;;) {
    do_task();        /* clearly the task loop */
}
while(0) statement; — what is the trap here?
The body never executes, so it is almost always a mistake or dead code; and without braces (Rule 15.6) it is easy to misread which statement the loop controls. Make the (non-)body explicit with {} and question why the loop exists at all.
Can you free() a pointer under MISRA if the memory was never from malloc?
The question is moot — Rule 21.3 bans free() itself along with the allocation functions. There is no compliant use of these <stdlib.h> memory functions to begin with.
If a wider intermediate (e.g. uint32_t scaled;) is used to avoid overflow before storing into int16_t, is that a MISRA problem?
No — using a wider unsigned intermediate is exactly the recommended pattern. The final narrowing back to int16_t should be explicit and bounds-checked (Rule 10.3) so no silent truncation occurs.

Recall Quick self-grade

If you gave any answer as a bare "true/false" or "yes/no" without a reason, revisit it — the reasoning is what MISRA compliance reviews actually demand. One-word answers hide misconceptions ::: Real understanding is shown by explaining the mechanism (dangling storage, implicit conversion, implementation-defined behaviour), not the verdict.