Skip to content

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_t so that they can be selected during overload resolution at compile-time, avoiding the need for a run-time check against nullptr.

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:

  1. They permit almost any type to be converted to almost any other type without checks
  2. They give no indication why the conversion is taking place
  3. 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:

  1. Enforce constraints on the types involved
  2. Give a better indication of the cast’s intent
  3. 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:

  1. The condition shall only be preceded by an optional simple-declaration
  2. case or default labeled statements shall only appear as part of a switch label group
  3. Switch label groups shall only contain case or default labeled statements
  4. The first statement in a switch body shall be a switch label group
  5. Every switch branch shall be unconditionally terminated by one of:
    • a. A break statement
    • b. A continue statement
    • c. A return statement
    • d. A goto statement (though it is a different violation)
    • e. A throw expression
    • f. A call to a [[noreturn]] function
    • g. A [[fallthrough]] attribute applied to a null statement
  6. Every switch statement shall have at least two switch branches
  7. Every switch statement shall have a default label, 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 switch has enum type, listing all enumerators in case labels does not make default redundant, 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
  • constexpr functions 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:

  1. __LINE__, __FILE__ or __func__
  2. 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