MISRA C:2012 is the most widely adopted coding standard for safety-critical embedded C development. Originally created for the automotive industry, it is now a baseline requirement across automotive, aerospace, medical devices, and industrial control systems.
This guide covers the most important MISRA C:2012 rules with practical C code examples - showing both violations and compliant alternatives - so you can understand what the rules enforce and how to write conforming code.
What is MISRA C?
MISRA C is a set of software development guidelines for the C programming language published by the Motor Industry Software Reliability Association (MISRA). The current edition - MISRA C:2012 - contains 175 guidelines designed to eliminate undefined behavior, implementation-defined behavior, and error-prone coding patterns from C programs.
The standard exists because C gives programmers enormous freedom, and that freedom is dangerous in systems where software bugs can cause physical harm. A buffer overflow in a web application might leak data. A buffer overflow in a brake controller can kill people.
MISRA C restricts C to a safer, more predictable subset of the language. It bans constructs that behave differently across compilers, eliminates patterns that commonly lead to bugs, and enforces practices that make code easier to review and verify.
Why MISRA C Matters
MISRA C compliance is expected or required in several safety-critical domains:
Automotive (ISO 26262): Every major automotive OEM and Tier-1 supplier requires MISRA C compliance for embedded ECU software. ISO 26262 functional safety certification references MISRA C as the recommended coding standard for C-based automotive software.
Aerospace and Defense (DO-178C): Avionics software certified under DO-178C frequently uses MISRA C as the coding standard. The strict traceability and verification requirements of DO-178C align well with MISRA's rule structure.
Medical Devices (IEC 62304): Software in medical devices classified under IEC 62304 benefits from MISRA C compliance to demonstrate safe coding practices during regulatory audits.
Industrial Control (IEC 61508): Programmable electronic safety systems governed by IEC 61508 use MISRA C to reduce the risk of systematic software failures.
Railway (EN 50128): Railway signaling and control software uses MISRA C as part of the verification process under EN 50128.
Even outside regulated industries, MISRA C adoption is growing in any embedded project where reliability matters - IoT devices, robotics, and firmware for critical infrastructure.
MISRA C:2012 Rule Categories
Every MISRA C:2012 guideline falls into one of three classification levels:
Mandatory Rules
These rules cannot be violated under any circumstance. No deviation is permitted. They address the most dangerous coding practices - things like accessing memory through null pointers or modifying loop counters inside the loop body.
Required Rules
These rules must be followed, but a formal deviation is allowed when compliance is not practical. Each deviation requires documented justification, a risk assessment, and approval from a qualified reviewer. Most MISRA C rules fall into this category.
Advisory Rules
These are recommended best practices. Teams can choose not to follow advisory rules without a formal deviation, but they should still document which advisory rules they exclude and why.
Additionally, MISRA C:2012 distinguishes between directives (general guidance that may require human judgment to verify) and rules (specific requirements that static analysis tools can check automatically).
Key MISRA C:2012 Rules with Code Examples
Let's walk through the most important rules with concrete C code showing violations and compliant alternatives.
Rule 1.3 (Mandatory) - No Undefined Behavior
There shall be no occurrence of undefined behavior. This is the foundational rule - if your code triggers undefined behavior as defined by the C standard, it violates MISRA C regardless of whether any other specific rule covers the construct.
/* VIOLATION - signed integer overflow is undefined behavior */
int32_t calculate_total(int32_t a, int32_t b)
{
return a + b; /* overflow possible if a + b > INT32_MAX */
}
/* COMPLIANT - check for overflow before the operation */
int32_t calculate_total(int32_t a, int32_t b)
{
int32_t result;
if ((b > 0) && (a > (INT32_MAX - b)))
{
result = INT32_MAX; /* saturate on overflow */
}
else if ((b < 0) && (a < (INT32_MIN - b)))
{
result = INT32_MIN; /* saturate on underflow */
}
else
{
result = a + b;
}
return result;
}
Rule 10.3 (Required) - No Implicit Narrowing Conversions
The value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category.
/* VIOLATION - implicit narrowing from int32_t to int16_t */
int32_t sensor_raw = read_sensor();
int16_t sensor_value = sensor_raw; /* data loss if sensor_raw > 32767 */
/* VIOLATION - assigning float to integer */
float temperature = 36.5f;
int32_t temp_display = temperature; /* truncates decimal part */
/* COMPLIANT - explicit cast shows intent */
int32_t sensor_raw = read_sensor();
int16_t sensor_value = (int16_t)sensor_raw; /* explicit narrowing */
/* COMPLIANT - explicit conversion with rounding */
float temperature = 36.5f;
int32_t temp_display = (int32_t)(temperature + 0.5f); /* round, then cast */
Rule 10.4 (Required) - Operands Must Have the Same Essential Type Category
Both operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type category.
/* VIOLATION - mixing signed and unsigned in comparison */
int32_t offset = -5;
uint32_t length = 100U;
if (offset < length) /* undefined: -5 converted to large unsigned value */
{
/* this branch may NOT execute as expected */
}
/* COMPLIANT - cast to a common type before comparison */
int32_t offset = -5;
uint32_t length = 100U;
if (offset < (int32_t)length) /* both operands are signed */
{
/* correct behavior */
}
Rule 11.3 (Required) - No Casts Between Pointer Types
A cast shall not be performed between a pointer to object type and a pointer to a different object type.
/* VIOLATION - casting between incompatible pointer types */
uint8_t buffer[4] = {0x01, 0x02, 0x03, 0x04};
uint32_t *word_ptr = (uint32_t *)buffer; /* alignment and aliasing issues */
uint32_t value = *word_ptr;
/* COMPLIANT - use memcpy to reinterpret bytes safely */
uint8_t buffer[4] = {0x01, 0x02, 0x03, 0x04};
uint32_t value;
(void)memcpy(&value, buffer, sizeof(value));
Rule 12.2 (Required) - Shift Operand Range
The right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operand.
/* VIOLATION - shifting by more bits than the type width */
uint16_t flags = 0x00FFU;
uint16_t result = flags << 16U; /* undefined: 16-bit type shifted by 16 */
/* VIOLATION - shift amount could be negative */
int8_t shift_amount = get_shift();
uint32_t result = 1U << shift_amount; /* negative shift is undefined */
/* COMPLIANT - shift amount validated and within range */
uint32_t flags = 0x000000FFU;
uint32_t result = flags << 16U; /* 32-bit type shifted by 16 is valid */
/* COMPLIANT - validate shift amount before use */
uint8_t shift_amount = get_shift();
uint32_t result = 0U;
if (shift_amount < 32U)
{
result = 1U << shift_amount;
}
Rule 13.5 (Required) - No Side Effects in Logical Operand Evaluation
The right hand operand of a logical && or || operator shall not contain persistent side effects.
/* VIOLATION - function with side effects in short-circuit expression */
if (is_valid(data) && process(data)) /* process() may not execute */
{
log_success();
}
/* COMPLIANT - evaluate separately to ensure both execute */
bool_t valid = is_valid(data);
bool_t processed = false;
if (valid)
{
processed = process(data);
}
if (valid && processed)
{
log_success();
}
This rule exists because the right operand of && or || might not be evaluated due to short-circuit evaluation. If that operand has side effects (modifying global state, writing to hardware registers, performing I/O), the program behavior depends on evaluation order - which is dangerous in safety-critical code.
Rule 14.2 (Required) - For Loop Counter Restrictions
A for loop shall be well-formed. The loop counter must not be modified in the loop body, the loop counter must be tested against a bound in the controlling expression, and the loop counter must be incremented or decremented in the third expression.
/* VIOLATION - loop counter modified inside the body */
for (uint32_t i = 0U; i < 100U; i++)
{
if (should_skip(i))
{
i += 2U; /* modifying loop counter in body */
}
process(i);
}
/* VIOLATION - missing increment in for expression */
for (uint32_t i = 0U; i < 100U; ) /* no third expression */
{
process(i);
i++; /* increment in body instead of for expression */
}
/* COMPLIANT - well-formed for loop */
for (uint32_t i = 0U; i < 100U; i++)
{
if (!should_skip(i))
{
process(i);
}
}
Rule 14.4 (Required) - Boolean Controlling Expressions
The controlling expression of an if statement and the controlling expression of an iteration statement shall have essentially Boolean type.
/* VIOLATION - integer used as boolean */
int32_t error_code = check_status();
if (error_code) /* non-boolean controlling expression */
{
handle_error();
}
/* VIOLATION - pointer used as boolean */
char *name = get_name();
if (name) /* non-boolean controlling expression */
{
display(name);
}
/* COMPLIANT - explicit comparison to produce boolean */
int32_t error_code = check_status();
if (error_code != 0)
{
handle_error();
}
/* COMPLIANT - explicit null check */
char *name = get_name();
if (name != NULL)
{
display(name);
}
Rule 15.5 (Advisory) - Single Point of Exit
A function should have a single point of exit at the end of the function.
/* VIOLATION - multiple return statements */
int32_t find_index(const int32_t *array, uint32_t size, int32_t target)
{
for (uint32_t i = 0U; i < size; i++)
{
if (array[i] == target)
{
return (int32_t)i; /* early return */
}
}
return -1;
}
/* COMPLIANT - single return at end */
int32_t find_index(const int32_t *array, uint32_t size, int32_t target)
{
int32_t result = -1;
for (uint32_t i = 0U; i < size; i++)
{
if (array[i] == target)
{
result = (int32_t)i;
break; /* Note: Rule 15.4 restricts break usage too */
}
}
return result;
}
Rule 17.7 (Required) - Return Values Must Be Used
The value returned by a function having non-void return type shall be used.
/* VIOLATION - ignoring the return value of fclose */
FILE *fp = fopen("config.dat", "r");
/* ... read data ... */
fclose(fp); /* return value discarded */
/* VIOLATION - ignoring return value of custom function */
uint8_t validate_input(const uint8_t *data, uint32_t length);
validate_input(sensor_data, data_len); /* return value ignored */
/* COMPLIANT - capture and use return values */
FILE *fp = fopen("config.dat", "r");
/* ... read data ... */
int close_result = fclose(fp);
if (close_result != 0)
{
report_file_error();
}
/* COMPLIANT - explicitly cast to void if intentionally ignoring */
(void)validate_input(sensor_data, data_len); /* deliberate discard */
Rule 20.4 (Required) - No Macro Redefinition
A macro shall not be defined with the same name as a keyword.
/* VIOLATION - redefining a keyword as a macro */
#define const /* removes const qualification throughout */
#define inline /* removes inline hint throughout */
#define true 1 /* redefines standard boolean */
/* COMPLIANT - use unique macro names that do not shadow keywords */
#define APP_CONST_QUALIFIER const
#define APP_TRUE ((bool)1)
Directive 4.1 (Required) - Run-time Failures Shall Be Minimized
This directive requires that code is written to minimize the possibility of run-time errors. It covers array bounds violations, division by zero, null pointer dereferences, and arithmetic overflow.
/* VIOLATION - no bounds check before array access */
void store_value(uint32_t *buffer, uint32_t index, uint32_t value)
{
buffer[index] = value; /* what if index >= buffer size? */
}
/* COMPLIANT - validate index before use */
#define BUFFER_SIZE 256U
void store_value(uint32_t *buffer, uint32_t index, uint32_t value)
{
if (index < BUFFER_SIZE)
{
buffer[index] = value;
}
else
{
report_error(ERROR_OUT_OF_BOUNDS);
}
}
Directive 4.12 (Required) - No Dynamic Memory Allocation
Dynamic memory allocation shall not be used. This means malloc, calloc, realloc, and free are all prohibited.
/* VIOLATION - using malloc in safety-critical code */
sensor_data_t *readings = (sensor_data_t *)malloc(count * sizeof(sensor_data_t));
if (readings != NULL)
{
/* process readings */
free(readings);
}
/* COMPLIANT - use statically allocated buffers */
#define MAX_READINGS 128U
static sensor_data_t readings[MAX_READINGS];
if (count <= MAX_READINGS)
{
/* process readings using static buffer */
}
else
{
report_error(ERROR_BUFFER_TOO_SMALL);
}
Dynamic memory introduces unpredictable behavior - fragmentation, allocation failures, and non-deterministic timing - that is unacceptable in safety-critical systems where every execution path must be analyzable.
Rule 21.3 (Required) - No stdlib.h Memory Functions
The memory allocation and deallocation functions of stdlib.h shall not be used. This rule reinforces Directive 4.12 with a specific, tool-checkable requirement.
Rule 21.6 (Required) - No Standard I/O
The Standard Library input/output functions shall not be used. Functions like printf, scanf, fopen, and fclose from stdio.h are banned because they have implementation-defined and undefined behaviors.
/* VIOLATION - using printf in embedded code */
void log_temperature(float temp)
{
printf("Temperature: %.1f\n", temp); /* banned */
}
/* COMPLIANT - use a custom logging interface */
void log_temperature(float temp)
{
int32_t temp_int = (int32_t)(temp * 10.0f);
uart_write_string("Temperature: ");
uart_write_int(temp_int);
uart_write_char('\n');
}
MISRA C:2012 Compliance Tools
Checking MISRA C compliance manually is impractical for any real project. Here are the leading tools that automate MISRA checking:
Polyspace Bug Finder and Code Prover (MathWorks)
Polyspace is the gold standard for MISRA compliance verification in automotive. Bug Finder performs fast static analysis to detect MISRA violations, while Code Prover uses abstract interpretation to mathematically prove the absence of certain runtime errors. Polyspace provides full MISRA C:2012 rule coverage and generates compliance reports accepted by certification bodies. It integrates with MATLAB/Simulink workflows, making it dominant in automotive where model-based development is common.
Best for: Automotive teams using MATLAB/Simulink, projects requiring formal verification.
PC-lint Plus (Gimpel Software)
PC-lint Plus is a lightweight, fast static analysis tool with comprehensive MISRA C checking. It runs locally and integrates into any build system. PC-lint has been a staple in embedded development for decades and covers nearly all decidable MISRA C:2012 rules. It is relatively affordable compared to other commercial tools.
Best for: Small to mid-size teams wanting affordable MISRA checking without heavyweight infrastructure.
Helix QAC (Perforce)
Helix QAC (formerly QA-C from PRQA) was one of the first tools specifically designed for MISRA compliance. It provides full MISRA C:2012 coverage, generates compliance matrices for audits, and includes a message browser for triaging findings. Helix QAC is widely used in European automotive and aerospace companies.
Best for: Teams needing certified MISRA compliance checking with audit-ready reporting.
Coverity (Synopsys)
Coverity is a heavyweight static analysis platform that includes MISRA C:2012 checking alongside its broader security and defect analysis capabilities. Coverity's interprocedural analysis engine can detect complex violations that span multiple functions and files. It scales to very large codebases and integrates with enterprise CI/CD pipelines.
Best for: Large organizations that need MISRA compliance as part of a broader code quality and security program.
Parasoft C/C++test
Parasoft provides MISRA C checking integrated with unit testing, code coverage, and requirements traceability. This all-in-one approach is attractive for teams that need to demonstrate compliance across the full V-model development lifecycle. Parasoft generates compliance reports mapped to specific safety standards like ISO 26262 and IEC 62304.
Best for: Teams following V-model development with integrated testing and compliance requirements.
Cppcheck (Open Source)
Cppcheck is a free, open-source static analysis tool that includes a MISRA C addon. The addon covers a subset of MISRA C:2012 rules - roughly 100 of the 159 rules. While not comprehensive enough for formal certification, Cppcheck is useful for teams starting their MISRA journey or as a pre-check before running commercial tools.
Best for: Open-source projects, teams evaluating MISRA adoption, or as a supplementary check.
LDRA TBvision
LDRA provides end-to-end verification tooling for safety-critical software. TBvision covers MISRA C checking, structural coverage analysis, requirements tracing, and test management. LDRA has deep roots in aerospace (DO-178C) and is qualified as a verification tool under multiple safety standards.
Best for: Aerospace and defense teams requiring DO-178C tool qualification.
Integrating MISRA Checking into CI/CD
Automated MISRA checking in your CI/CD pipeline catches violations before they reach the main branch. Here is how to set it up effectively:
Step 1: Establish Your Rule Profile
Not every project needs to comply with all 175 guidelines. Create a configuration file that specifies which rules your project enforces:
/* misra_config.txt - project MISRA rule selection */
/* All mandatory rules are always enforced */
/* Required rules - enforced with deviation process */
Rule 10.3: enabled
Rule 10.4: enabled
Rule 11.3: enabled
Rule 12.2: enabled
Rule 14.2: enabled
Rule 14.4: enabled
Rule 17.7: enabled
/* Advisory rules - selected subset */
Rule 15.5: enabled
Rule 15.6: enabled
Rule 15.7: enabled
Step 2: Add MISRA Checking to Your Build Pipeline
Here is a GitHub Actions example using Cppcheck's MISRA addon as a starting point:
# .github/workflows/misra-check.yml
name: MISRA C Compliance Check
on:
pull_request:
paths:
- '**.c'
- '**.h'
jobs:
misra-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Cppcheck
run: |
sudo apt-get update
sudo apt-get install -y cppcheck
- name: Run MISRA C check
run: |
cppcheck \
--addon=misra \
--suppress=missingIncludeSystem \
--error-exitcode=1 \
--inline-suppr \
-I include/ \
src/
- name: Upload results
if: failure()
uses: actions/upload-artifact@v4
with:
name: misra-violations
path: misra-report.txt
For commercial tools like Polyspace or Helix QAC, the CI integration typically involves a containerized version of the tool running in your pipeline:
- name: Run Polyspace Bug Finder
run: |
polyspace-bug-finder \
-sources src/ \
-I include/ \
-misra3 mandatory-required \
-results-dir ./polyspace-results
- name: Check for MISRA violations
run: |
polyspace-report-generator \
-results-dir ./polyspace-results \
-output-format csv \
-report misra-compliance.csv
Step 3: Gate Merges on MISRA Compliance
Configure your CI to block pull requests that introduce new MISRA violations. Use baseline files to track existing violations separately from new ones - this lets you adopt MISRA incrementally on legacy codebases without requiring a full rewrite before you can enforce the rules.
Step 4: Track Deviations in Version Control
Store deviation records alongside the code they apply to:
/* DEVIATION: Rule 11.3 - Required
* Justification: Hardware register access requires pointer cast
* to volatile uint32_t at fixed memory address.
* Risk assessment: Address is validated at system startup.
* Approved by: Safety team review 2026-01-15
*/
volatile uint32_t *gpio_port = (volatile uint32_t *)0x40020000U; /* cppcheck-suppress misra-c2012-11.3 */
Common MISRA C Adoption Mistakes
Trying to comply with every rule on day one. Start with mandatory and required rules. Add advisory rules incrementally as your team builds familiarity.
Ignoring the deviation process. MISRA was designed with deviations in mind. Some rules genuinely cannot be followed in every situation - particularly in low-level hardware interface code. A well-documented deviation is better than a code workaround that introduces its own risks.
Using only one tool. No single static analysis tool catches every MISRA violation. Many teams use two tools - for example, Helix QAC for primary compliance checking and Cppcheck as a fast pre-commit check.
Not training the team. MISRA compliance is not just a tool configuration. Developers need to understand why each rule exists so they write naturally compliant code instead of fighting the tool output after the fact.
Treating MISRA as a checkbox. The goal is safer code, not a clean report. Suppressing findings without understanding them defeats the purpose of the standard.
MISRA C:2012 Amendment 4 and Beyond
MISRA C:2012 has been updated through several amendments and technical corrigenda:
- Amendment 1 (2016): Added guidelines for C11 language features
- Amendment 2 (2020): Introduced new security-focused rules
- Amendment 3 (2023): Further security rules and CERT C mapping
- Amendment 4 (2025): Additional rules covering concurrency and multi-threaded code
The MISRA committee also published MISRA C:2012 Addendum 3 which maps MISRA C rules to CERT C rules, helping teams that need to comply with both standards simultaneously.
When configuring your tools, make sure you are checking against the latest amendment level your project requires. Tool vendors typically release updates to match new amendments within a few months of publication.
Wrapping Up
MISRA C:2012 is not just a set of arbitrary restrictions - each rule addresses a real class of bugs that has caused failures in safety-critical systems. The rules around undefined behavior, type safety, pointer usage, and control flow exist because these are the exact areas where C programs fail in dangerous ways.
If you are starting a new embedded C project in a regulated industry, adopt MISRA C:2012 from the beginning. If you are retrofitting MISRA compliance onto an existing codebase, start with mandatory rules, use baseline files to manage existing violations, and expand coverage incrementally.
The investment in MISRA compliance pays off not just in passing audits, but in genuinely more reliable software. Code that follows MISRA C is easier to review, easier to test, and far less likely to contain the subtle bugs that only manifest in production under edge-case conditions.
For teams evaluating static analysis tools that include MISRA checking, the choice between tools often comes down to which safety standard you are targeting, whether you need formal tool qualification, and how the tool fits into your existing build and CI/CD infrastructure.
Frequently Asked Questions
What is the difference between MISRA C:2004 and MISRA C:2012?
MISRA C:2012 is the successor to MISRA C:2004 and includes significant improvements. It expanded from 142 rules to 175 rules (including directives), added a new 'mandatory' classification level above 'required,' improved the rationale and examples for each rule, and introduced the concept of decidable vs undecidable rules. MISRA C:2012 also added support for C99 language features while retaining C90 compatibility. Teams starting new projects should always use MISRA C:2012 rather than the older 2004 edition.
Is MISRA C compliance legally required?
MISRA C compliance is not a law, but it is effectively required in certain industries through functional safety standards. ISO 26262 for automotive software references MISRA C as a recommended coding standard. IEC 62304 for medical device software and DO-178C for avionics software also reference or strongly recommend MISRA-compliant coding practices. In practice, if your product must meet these safety standards, auditors and certification bodies expect MISRA C compliance as evidence of safe coding practices.
How many rules are in MISRA C:2012?
MISRA C:2012 contains 175 guidelines split into two types - 16 directives and 159 rules. Directives provide general guidance that may not be fully checkable by static analysis tools, while rules are specific and verifiable. These are further categorized as mandatory (cannot be deviated under any circumstance), required (deviations allowed with documented justification), and advisory (recommended best practices that teams may choose not to follow).
Can I use dynamic memory allocation with MISRA C?
MISRA C:2012 Directive 4.12 classifies dynamic memory allocation (malloc, calloc, realloc, free) as required to avoid. The concern is that heap allocation introduces risks of memory leaks, fragmentation, and non-deterministic behavior - all dangerous in safety-critical systems. If your project must use dynamic allocation, you need a formal deviation with documented justification, a custom allocator with bounded behavior, or allocation only during initialization with no runtime freeing.
What tools can check MISRA C:2012 compliance?
Several commercial and open-source tools check MISRA C:2012 compliance. Leading commercial tools include Polyspace Bug Finder and Code Prover (MathWorks), PC-lint Plus (Gimpel Software), Helix QAC (Perforce), Coverity (Synopsys), Parasoft C/C++test, and LDRA TBvision. For open-source options, Cppcheck provides partial MISRA checking with its addon, and some teams use Clang-Tidy with custom configurations. Commercial tools generally provide higher rule coverage and certified compliance checking required for safety certification audits.
How do I handle MISRA C deviations?
MISRA C:2012 provides a formal deviation process for required and advisory rules that cannot be followed in specific situations. Each deviation must include the rule being deviated, the justification explaining why compliance is not possible or practical, a risk assessment showing the deviation does not compromise safety, alternative measures taken to mitigate the risk, and approval from a qualified reviewer. Deviations must be documented and tracked. Mandatory rules cannot be deviated under any circumstance.
Originally published at aicodereview.cc
Top comments (0)