CertC-PRE05¶
Understand macro replacement when concatenating tokens or performing stringification
Required inputs: IR
It is necessary to understand how macro replacement works in C, particularly in
the context of concatenating tokens using the
## operator and converting macro parameters to strings using the
# operator.
Concatenating Tokens
The
## preprocessing operator is used to merge two tokens into one
while expanding macros, which is called token pasting or token
concatenation. When a macro is expanded, the two tokens on either side of
each
## operator are combined into a single token that replaces the
## and the two original tokens in the macro expansion [
FSF
2005].
Token pasting is most useful when one or both of the tokens come from a macro
argument. If either of the tokens next to a
## is a parameter name, it is replaced by its actual argument
before
## executes. The actual argument is not macro expanded first.
Stringification
Parameters are not replaced inside string constants, but the
# preprocessing operator can be used instead. When a macro
parameter is used with a leading
#, the preprocessor replaces it with the literal text of the
actual argument converted to a string constant [
FSF
2005].
Noncompliant Code Example
The following definition for
static_assert() from
DCL03-C.
Use a static assertion to test the value of a constant expression uses the
JOIN() macro to concatenate the token
assertion_failed_at_line_ with the value of
__LINE__:
#define static_assert(e) \
typedef char JOIN(assertion_failed_at_line_, __LINE__) \
[(e) ? 1 : -1]
__LINE__ is a predefined macro name that expands to an
integer constant representing the presumed line number of the current source
line within the current source file. If the intention is to expand the
__LINE__ macro, which is likely the case here, the following
definition for
JOIN() is noncompliant because the
__LINE__ is not expanded, and the character array is subsequently
named
assertion_failed_at_line___LINE__:
#define JOIN(x, y) x ## y
Compliant Solution
To get the macro to expand, a second level of indirection is required, as shown by this compliant solution:
#define JOIN(x, y) JOIN_AGAIN(x, y) #define JOIN_AGAIN(x, y) x ## y
JOIN(x, y) calls
JOIN_AGAIN(x, y) so that if
x or
y is a macro, it is expanded before the
## operator pastes them together.
Note also that macro parameters cannot be individually parenthesized when
concatenating tokens using the
## operator, converting macro parameters to strings using the
# operator, or concatenating adjacent string literals. This is an
exception, PRE01-C-EX2, to
PRE01-C.
Use parentheses within macros around parameter names.
Noncompliant Code Example
This example is noncompliant if the programmer's intent is to expand the macro before stringification:
#define str(s) #s #define foo 4 str(foo)
The macro invocation
str(foo) expands to
foo.
Compliant Solution
To stringify the result of expansion of a macro argument, two levels of macros must be used:
#define xstr(s) str(s) #define str(s) #s #define foo 4
The macro invocation
xstr(foo) expands to
4 because
s is stringified when it is used in
str(), so it is not macro expanded first. However,
s is an ordinary argument to
xstr(), so it is completely macro expanded before
xstr() is expanded. Consequently, by the time
str() gets to its argument, it has already been macro expanded.
Risk Assessment
| Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| PRE05-C | Low | Unlikely | Medium | P2 | L3 |
Related Guidelines
| SEI CERT C++ Coding Standard | VOID PRE05-CPP. Understand macro replacement when concatenating tokens or performing stringification |
Bibliography
| [ FSF 2005] | Section 3.4, "
Stringification" Section 3.5, " Concatenation" |
| [ Saks 2008] |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
mixed_macro_arg_substitution |
Macro parameter {} is used both normally and with # or ## (where macro expansion is suppressed for the macro passed as argument for it at the positions listed below) |
None |
False |
unexpanded_macro_as_argument |
Macro parameter {} is used with # or ##, so macro expansion is suppressed for the macro passed as argument for it at the positions listed below |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
report_only_mixed_uses¶
report_only_mixed_uses : bool = False
# or
##.