MISRA C++ 2023
Warning
The following rules are inspired by MISRA C++ 2023 and used in the Custom Checker for this course. These can contain errors and should be used very carefully and only in educational context.
This is a free, open-source Python tool that checks C++ source code against
a representative subset of MISRA C++ 2023 rules using regex/text analysis
and (optionally) libclang AST.
⚠️ Disclaimer
Rule IDs are approximate mappings and some are created
by the authors.
Always verify against the official MISRA C++ 2023 specification should you use them professionally.
This tool does not replace a certified MISRA checker (LDRA, PVS, PC-lint Plus, etc.).
Rules
Below the rules we are using in the course.
Rule 7.11.1 — nullptr shall be the only form of the null-pointer-constant
| Property | Value |
|---|---|
| Category | Required |
| Analysis | Decidable, Single Translation Unit |
| References | [support.types.nullptr], Implementation 2 |
Amplification
Using any integral literal representing zero, including the literal 0 or the macro NULL, to represent the null-pointer-constant is a violation of this rule.
In addition, the macro NULL shall not be used in any other context.
Definition / Rationale
The C++ Standard defines the object nullptr as the null-pointer-constant.
The literal 0 can also be used to represent a null-pointer-constant. However, 0 has type int, and its use can lead to unexpected overload resolution. Note that the macro NULL expands to 0.
Note: some library functions provide overloads for
std::nullptr_tso that they can be selected during overload resolution at compile-time, avoiding the need for a run-time check againstnullptr.
Examples
nullptr vs 0 as null-pointer-constant
void f1( int32_t * );
void f2()
{
f1( nullptr ); // Compliant
f1( 0 ); // Non-compliant — 0 used as the null pointer constant
}
NULL causing incorrect overload resolution
void f3( int32_t );
void f3( int32_t * );
void f4()
{
f3( NULL ); // Non-compliant — calls the int32_t overload
f3( nullptr ); // Compliant — calls the int32_t * overload
}
NULL used in other contexts
#define MYNULL NULL // Non-compliant
void f5()
{
int32_t one = NULL + 1; // Non-compliant — NULL used as an integer
throw NULL; // Non-compliant — caught by catch ( int )
}
Rule 8.2.2 — C-style casts and functional notation casts shall not be used
| Property | Value |
|---|---|
| Category | Required |
| Analysis | Decidable, Single Translation Unit |
| References | [expr.type.conv], [expr.cast] |
Amplification
This rule does not apply to functional notation casts that use curly braces or that result in a constructor call.
Definition / Rationale
C-style casts and functional notation casts raise several concerns:
- They permit almost any type to be converted to almost any other type without checks
- They give no indication why the conversion is taking place
- Their syntax is more difficult to recognize
These concerns can be addressed with the use of const_cast, dynamic_cast, static_cast and reinterpret_cast, which:
- Enforce constraints on the types involved
- Give a better indication of the cast’s intent
- Are easy to identify
Exception
A C-style cast to void is permitted, as this allows the intentional discarding of a value to be made explicit — for instance, the return value of a non-void function call (see Rule 0.1.2).
Examples
Compliant by exception — cast to void
int32_t g();
void f1()
{
( void ) g(); // Compliant by exception — discarding return value explicitly
// The developer intentionally discards the return value and
// this is the sole case a c_style casting is accepted
}
Non-compliant — C-style cast between struct types
struct A
{
A( char c );
};
struct B {};
void f1a( A x )
{
auto const & a1 = x;
A * a2 = ( A * )&a1; // Non-compliant
A * a3 = const_cast< A * >( &a1 );
}
void f1b( B x )
{
auto const & a1 = x;
A * a2 = ( A * )&a1; // Non-compliant
A * a3 = const_cast< A * >( &a1 ); // Ill-formed
}
Functional notation casts
void f2( int32_t x )
{
auto i = A( 'c' ); // Rule does not apply — constructor is called
auto j = int8_t { 42 }; // Rule does not apply — use of curly braces
auto k = int8_t ( x ); // Non-compliant — does not construct an object
} // of class type
See also
Rule 0.1.2, Rule 8.2.3
Rule 9.4.2 — The structure of a switch statement shall be appropriate
| Property | Value |
|---|---|
| Category | Required |
| Analysis | Decidable, Single Translation Unit |
| References | [stmt.switch], [dcl.attr.fallthrough] |
Definition / Rationale
The substatement of a switch statement is called the switch body. It shall be a compound statement.
A labeled statement, along with the complete chain of its substatements that are also labeled statements, is called a label group. A label group directly enclosed by a switch body is called a switch label group.
The statements directly enclosed by a switch body are partitioned into switch branches, with each switch label group starting a new branch.
Restrictions
A switch statement is structured appropriately when all of the following hold:
- The condition shall only be preceded by an optional simple-declaration
caseordefaultlabeled statements shall only appear as part of a switch label group- Switch label groups shall only contain
caseordefaultlabeled statements - The first statement in a switch body shall be a switch label group
- Every switch branch shall be unconditionally terminated by one of:
- a. A
breakstatement - b. A
continuestatement - c. A
returnstatement - d. A
gotostatement (though it is a different violation) - e. A
throwexpression - f. A call to a
[[noreturn]]function - g. A
[[fallthrough]]attribute applied to a null statement
- a. A
- Every
switchstatement shall have at least two switch branches - Every
switchstatement shall have adefaultlabel, appearing as either the first label of the first switch label group or the last label of the last switch label group
Exception
If the condition of a switch statement is an unscoped enumeration type that does not have a fixed underlying type, and all the enumerators are listed in case labels, then a default label is not required.
Note: even when the condition of a
switchhasenumtype, listing all enumerators incaselabels does not makedefaultredundant, as the value could still lie outside the set of enumerators.
Examples
Compliant
switch ( int8_t x = f(); x ) // Compliant — simple-declaration before condition
{
case 1:
{
break; // Compliant — unconditionally terminated
}
case 2:
case 3:
throw; // Compliant — unconditionally terminated
case 4:
a++;
[[fallthrough]]; // Compliant — explicit fall-through attribute
default: // Compliant — default is last label
b++;
return; // Compliant — unconditionally terminated
}
Non-compliant
switch ( x = f(); x ) // Non-compliant — x = f() is not a simple-declaration
{
int32_t i; // Non-compliant — not a switch label group
case 5:
if ( ... )
{
break;
}
else
{
break;
}
// Non-compliant — termination is not unconditional
case 6:
a = b; // Non-compliant — non-empty implicit fall-through
case 7:
{
case 8: // Non-compliant — case not in a switch label group
DoIt();
}
break;
} // Non-compliant — default is required
Additional non-compliant patterns
switch ( x ) // Non-compliant — only one switch branch
{
default:
; // Non-compliant — default must also be terminated
}
switch ( colour )
{
case RED:
break;
case GREEN:
break;
} // Non-compliant — default is required
switch ( colour ) // Non-compliant — only one switch branch
{
case RED:
default: // Non-compliant — default must be first or last label
case BLUE:
break;
}
Compliant by exception
enum Colours { RED, GREEN, BLUE } colour;
switch ( colour ) // Compliant by exception — all enumerators listed,
{ // unscoped enum without fixed underlying type
case RED:
case GREEN:
break;
case BLUE:
break;
}
See also
Rule 9.4.1, Rule 9.6.2, Rule 9.6.3, Rule 10.2.3
Rule 19.0.2 — Function-like macros shall not be defined
| Property | Value |
|---|---|
| Category | Required |
| Analysis | Decidable, Single Translation Unit |
Definition / Rationale
Functions have a number of advantages over function-like macros, including:
- Function arguments and return values are type-checked
- Function arguments are evaluated once, preventing problems with potential multiple side effects
- Function names follow classical scoping rules
- Functions can be overloaded and templatized
- The address of a function can be passed to another function
- Function calls can be inlined, providing the same performance characteristics as macros
constexprfunctions can be evaluated at compile-time and may be used in all contexts where a compile-time constant is required- In many debugging systems, it is easier to step through execution of a function than a macro
Exception
As it is not possible to implement equivalent behaviour within a function, a function-like macro may be defined if its definition includes any of the following:
__LINE__,__FILE__or__func__- The
#or##operators
Examples
Non-compliant
#define FUNC( X ) \
( ( X ) + ( X ) ) // Non-compliant
Possible alternative
template< typename T >
constexpr auto func( T x ) // Possible alternative
{
return x + x;
}
Compliant by exception
#define ID( name ) \
constexpr auto name = #name; // Compliant — use of #
#define TAG( name ) \
class name##Tag {}; // Compliant — use of ##
See also
Rule 19.3.1
Rule 21.6.1 — Dynamic memory should not be used
| Property | Value |
|---|---|
| Category | Advisory |
| Analysis | Undecidable, Single Translation Unit |
Amplification
Dynamic memory refers to any object with dynamic storage duration that is
managed using operator new (excluding the non-allocating placement versions),
operator delete, the functions calloc, malloc, realloc, aligned_alloc
and free, or any platform-specific memory allocation or deallocation function.
Uses of dynamic memory may occur implicitly (e.g., when throwing exceptions or
using C++ Standard Library classes). Therefore, any instantiation of a C++
Standard Library entity having a template argument that is a specialization of
std::allocator is a violation of this rule, as is any call to a C++ Standard
Library function that may use dynamic memory.
Definition / Rationale
It is acknowledged that applications may need to use dynamic memory, leading to violations of this rule. Any uses of dynamic memory need to be justified through supporting documentation that explains how the issues that have been identified in Section 4.21.6 are managed within the project.
Note: a project may reclassify this rule (see MISRA Compliance [1]) if the risks related to the use of dynamic memory are considered to be unacceptable.
Example
auto i = std::make_unique< int32_t >( 42 ); // Non-compliant
auto j = std::vector< int32_t > {}; // Non-compliant