This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 19.3.4 - Parentheses shall be used to ensure macro arguments are expanded appropriately
[Koenig] 78–81
Category: Required
Analysis: Decidable,Single Translation Unit
Amplification
For the purposes of this rule, a critical operator is an operator that has a ranking between 2 and 13 (inclusive), as specified in the
table to M23_066: MISRA C++ 2023 Rule 8.0.1.
A macro argument containing a top-level token (see definition below) that expands as a critical operator is inappropriately
expanded if, within the macro definition, there is an occurrence of the corresponding macro parameter that is not:
- Directly parenthesized (a parameter
x is directly parenthesized in ( x )); or
- Stringified (used as an operand to the
# operator).
When a macro is expanded, a level can be associated with every token in the expansion of a macro argument. For each argument, the level of
its first token is zero, and then the level of each of its subsequent tokens relative to the level of the previous token is:
- One more, if the previous token is
(
- One less, if the previous token is
)
- The same, for any other previous token.
A token is said to be top-level when its level is less than or equal to zero.
Rationale
When a macro is invoked with an argument that looks like an expression, it is generally assumed that this expression will behave as if it were an
argument to a function call — in particular, that it will be evaluated in isolation.
However, since macro expansion result in textual replacement, a macro parameter is simply replaced by the text corresponding to the argument. This
means that the different tokens that form the argument can end up forming parts of different sub-expressions. This typically happens when the argument
contains an operator having a low precedence, and the parameter is expanded next to an operator having a higher precedence. This behaviour can
generally be avoided by adding parentheses around the macro parameter.
Example
In the following example, the operator + is a top-level token in the x argument to the macro. However,
x is neither parenthesized nor stringified in the macro definition. The value of the resulting expression is 7, whereas the value 9 might
have been expected.
#define M1( x, y ) ( x * y )
r = M1( 1 + 2, 3 ); // Non-compliant - x not parenthesized
// Expands as r = ( 1 + 2 * 3 );
Ideally, the above can be re-written in a compliant manner by parenthesizing the macro parameters in the macro definition:
#define M2( x, y ) ( ( x ) * ( y ) )
r = M2( 1 + 2, 3 ); // Compliant - x is directly parenthesized
// Expands as r = ( ( 1 + 2 ) * ( 3 ) );
If this is not possible, it is also acceptable to parenthesize the macro argument:
r = M1( ( 1 + 2 ), 3 ); // Compliant - operator + is not top-level
// Expands as r = ( ( 1 + 2 ) * 3 );
In the following example, the macro M1 is invoked with 1 + 2 as its x parameter, and the top level
+ token is a critical operator. Therefore, x is inappropriately expanded, as it is neither parenthesized nor
stringified in the macro definition.
#define M3( z ) z + 2
r = M1( M3( 1 ), 3 ); // Non-compliant - operator + is top-level
// Expands as r = ( 1 + 2 * 3 );
Given the macro definition:
#define MY_ASSERT( cond ) \
do \
{ \
if ( !cond ) \
{ \
std::cerr << #cond << " failed!\n"; \
std::abort(); \
} \
} while( false )
and its use:
int32_t x = 0;
MY_ASSERT( x < 42 ); // Non-compliant - argument expansions result in:
// if ( !x < 42 ) - neither parenthesized nor stringified
// "!x < 42" - stringified
During expansion of MY_ASSERT, the cond parameter is replaced by the argument x < 42. This argument includes
< as a top-level token that expands as a critical operator, which means that all occurrences of cond in the
macro definition have to be checked for compliance. Within the macro, cond is used:
- As the operand to
#, which is compliant as it is stringified; and
- Within
if( !cond ), which is non-compliant as it is neither parenthesized nor stringified — the macro expansion will contain
if ( !x < 42 ), which is true for any value of x (it is equivalent to if ( (!x) < 42 )).
Similarly, MY_ASSERT( a or b ) would also be non-compliant as the rule applies irrespective of the way in which an operator is
spelled.
The following example is compliant as the < and > tokens are not operators in the expanded code.
#define PROP( Type, Name ) \
Type Name; \
Type get_##Name() { return Name; }
struct Student
{
PROP( vector< int32_t >, grades );
}
Copyright The MISRA Consortium Limited © 2023