Question bank — MISRA C — rules for safety-critical C code
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.


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

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

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

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.
MISRA C:2012 forbids a function from having more than one return statement.
A "Required" rule can never be violated in compliant code.
Following MISRA C guarantees your program is free of bugs.
MISRA C is a compiler that checks your code.
Because bitwise operators are banned on signed types, MISRA effectively bans bit manipulation.
unsigned types, not to abandon masks and shifts.Rule 21.3 (no malloc/free) exists because dynamic memory is slow.
A rule number is the same across MISRA C:2004 and C:2012.
Static allocation is always safer than stack allocation under MISRA.
An Advisory rule can be ignored silently with no record.
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"?
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?
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?
{} 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?
& 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?
-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?
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?
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?
if (adc > 0U) and buf[0] = adc & 0xFFU;.Stage 3 — fix Rule 15.6: what wraps the body?
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?
Why is single-exit still recommended even though C:2012 downgraded it to Advisory?
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?
Why introduce the essential type model instead of just using C's built-in types?
Why is MISRA especially relevant to standards like ISO 26262, DO-178C, and IEC 62304?
Why does banning the heap improve real-time behaviour on an RTOS?
Why does the Mandatory category exist at all if Required already needs sign-off to break?
Edge cases
Is p + 1 where p points to the last element of an array a violation of Rule 18.1?
What about arithmetic on a pointer to a single scalar that is not an array element?
&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?
;, 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?
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?
{} and question why the loop exists at all.Can you free() a pointer under MISRA if the memory was never from malloc?
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?
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.