...
Code Block | ||||
---|---|---|---|---|
| ||||
#define CUBE(X) ((X) * (X) * (X)) /* ... */ void func(void) { int i = 2; int a = 81 / CUBE(++i); /* ... */ } |
For this example, the initialization for a
expands to
Code Block | |
---|---|
#FFCCCC | |
lang | c | int a = 81 / ((++i) * (++i) * (++i));
|
which is undefined . (See see EXP30-C. Do not depend on the order of evaluation between sequence points.)for side effects).
Compliant Solution
When the macro definition is replaced by an inline function, the side effect is executed only once before the function is called:
Code Block | ||||
---|---|---|---|---|
| ||||
inline int cube(int i) { return i * i * i; } /* ... */ void func(void) { int i = 2; int a = 81 / cube(++i); /* ... */ } |
Noncompliant Code Example
...
Unlike functions, the execution of macros can interleave. Consequently, two macros that are harmless in isolation can cause undefined behavior when combined in the same expression. In this example, F()
and G()
both increment the global variable operations
, which causes problems when the two macros are used together:
Code Block | ||||
---|---|---|---|---|
| ||||
int operations = 0, calls_to_F = 0, calls_to_G = 0; #define F(x) (++operations, ++calls_to_F, 2 * x) #define G(x) (++operations, ++calls_to_G, x + 1) /* ... */ void func(int x) { int y = F(x) + G(x); } |
The variable operations
is both read and modified twice in the same expression, so it can receive the wrong value if, for example, the following ordering occurs:
...
This noncompliant code example also violates EXP30-C. Do not depend on the order of evaluation between sequence pointsfor side effects.
Compliant Solution
The execution of functions, including inline functions, cannot be interleaved, so problematic orderings are not possible:
Code Block | ||||
---|---|---|---|---|
| ||||
int operations = 0, calls_to_F = 0, calls_to_G = 0; inline int f(int x) { ++operations; ++calls_to_fF; return 2 * x; } inline int g(int x) { ++operations; ++calls_to_gG; return x + 1; } /* ... */ void func(int x) { int y = f(x) + g(x); } |
Platform-Specific Details
GNU C (and some other compilers) supported inline functions before they were added to the C Standard and, as a result, have significantly different semantics. Richard Kettlewell provides a good explanation of differences between the C99 and GNU C rules [Kettlewell 2003].
Exceptions
PRE00-C-EX1: Macros can be used to implement local functions (repetitive blocks of code that have access to automatic variables from the enclosing scope) that cannot be achieved with inline functions.
PRE00-C-EX2: Macros can be used for concatenating tokens or performing stringification. For example,
...
calculates only one of the two expressions depending on the selector's value. See PRE05-C. Understand macro replacement when concatenating tokens or performing stringification for more information.
PRE00-C-EX3: Macros can be used to yield a compile-time constant. This is not always possible using inline functions, as shown by the following example:
...
In this example, the ADD_M(3,4)
macro invocation yields a constant expression, while but the add_f(3,4)
function invocation does not.
PRE00-C-EX4: Macros can be used to implement type-generic functions that cannot be implemented in the C language without the aid of a mechanism such as C++ templates.
An example of the use of function-like macros to create type-generic functions is shown in MEM02-C. Immediately cast the result of a memory allocation function call into a pointer to the allocated type.
Type-generic macros may also be used, for example, to swap two variables of any type, provided they are of the same type.
PRE00-C-EX5: Macro parameters exhibit call-by-name semantics, whereas functions are call by value. Macros must be used in cases where call-by-name semantics are required.
...
Improper use of macros may result in undefined behavior.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
PRE00-C |
Medium |
Unlikely |
Medium | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| macro-function-like | Fully checked | ||||||
Axivion Bauhaus Suite |
| CertC-PRE00 | |||||||
CodeSonar |
| LANG.PREPROC.FUNCMACRO | Function-Like Macro | ||||||
|
CC2.PRE00 | Fully implemented | ||||||||
Helix QAC |
| C3453 | |||||||
Klocwork |
| MISRA.DEFINE.FUNC | |||||||
LDRA tool suite |
| 340 S |
Fully implemented
Enhanced enforcement | |||||||||
Parasoft C/C++test |
| CERT_C-PRE00-a | A function should be used in preference to a function-like macro | ||||||
PC-lint Plus |
| 9026 | Assistance provided | ||||||
Polyspace Bug Finder |
| CERT C: Rec. PRE00-C | Checks for use of function-like macro instead of function (rec. fully covered) | ||||||
RuleChecker |
| macro-function-like function-like-macro-expansion | Fully checked | ||||||
SonarQube C/C++ Plugin |
| S960 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
SEI CERT C++ |
Coding Standard | VOID PRE00-CPP. Avoid defining macros |
ISO/IEC TR 24772:2013 | Pre-processor Directives [NMP] |
MISRA C:2012 | Directive 4.9 (advisory) |
Bibliography
[Dewhurst 2002] | Gotcha #26, "#define Pseudofunctions" |
[FSF 2005] | Section 5.34, "An Inline Function Is as Fast as a Macro" |
[Kettlewell 2003] |
[Summit 2005] | Question 10.4 |
...
...