Skip to end of metadata
Go to start of metadata

Macros are dangerous because their use resembles that of real functions, but they have different semantics. The inline function-specifier was introduced to the C programming language in the C99 standard. Inline functions should be preferred over macros when they can be used interchangeably. Making a function an inline function suggests that calls to the function be as fast as possible by using, for example, an alternative to the usual function call mechanism, such as inline substitution. (See also PRE31-C. Avoid side effects in arguments to unsafe macros, PRE01-C. Use parentheses within macros around parameter names, and PRE02-C. Macro replacement lists should be parenthesized.)

Inline substitution is not textual substitution, nor does it create a new function. For example, the expansion of a macro used within the body of the function uses the definition it had at the point the function body appeared, not where the function is called; and identifiers refer to the declarations in scope where the body occurs.

Arguably, a decision to inline a function is a low-level optimization detail that the compiler should make without programmer input. The use of inline functions should be evaluated on the basis of (a) how well they are supported by targeted compilers, (b) what (if any) impact they have on the performance characteristics of your system, and (c) portability concerns. Static functions are often as good as inline functions and are supported in C.

Noncompliant Code Example

In this noncompliant code example, the macro CUBE() has undefined behavior when passed an expression that contains side effects:

#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

int a = 81 / ((++i) * (++i) * (++i));

which is undefined (see EXP30-C. Do not depend on the order of evaluation 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:

inline int cube(int i) {
  return i * i * i;
}
 
void func(void) {
  int i = 2;
  int a = 81 / cube(++i);
  /* ... */ 
}

Noncompliant Code Example

In this noncompliant code example, the programmer has written a macro called EXEC_BUMP() to call a specified function and increment a global counter [Dewhurst 2002]. When the expansion of a macro is used within the body of a function, as in this example, identifiers refer to the declarations in scope where the body occurs. As a result, when the macro is called in the aFunc() function, it inadvertently increments a local counter with the same name as the global variable. Note that this example also violates DCL01-C. Do not reuse variable names in subscopes.

size_t count = 0;

#define EXEC_BUMP(func) (func(), ++count)

void g(void) {
  printf("Called g, count = %zu.\n", count);
}

void aFunc(void) {
  size_t count = 0;
  while (count++ < 10) {
    EXEC_BUMP(g);
  }
}

The result is that invoking aFunc() (incorrectly) prints out the following line five times:

Called g, count = 0.

Compliant Solution

In this compliant solution, the EXEC_BUMP() macro is replaced by the inline function exec_bump(). Invoking aFunc() now (correctly) prints the value of count ranging from 0 to 9:

size_t count = 0;

void g(void) {
  printf("Called g, count = %zu.\n", count);
}

typedef void (*exec_func)(void);
inline void exec_bump(exec_func f) {
  f();
  ++count;
}

void aFunc(void) {
  size_t count = 0;
  while (count++ < 10) {
    exec_bump(g);
  }
}

The use of the inline function binds the identifier count to the global variable when the function body is compiled. The name cannot be re-bound to a different variable (with the same name) when the function is called.

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:

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:

read operations into register 0
read operations into register 1
increment register 0
increment register 1
store register 0 into operations
store register 1 into operations

This noncompliant code example also violates EXP30-C. Do not depend on the order of evaluation for side effects.

Compliant Solution

The execution of functions, including inline functions, cannot be interleaved, so problematic orderings are not possible:

int operations = 0, calls_to_F = 0, calls_to_G = 0;
 
inline int f(int x) {
  ++operations;
  ++calls_to_F;
  return 2 * x;
}

inline int g(int x) {
  ++operations;
  ++calls_to_G;
  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,

enum Color { Color_Red, Color_Green, Color_Blue };
static const struct {
  enum Color  color;
  const char *name;
} colors[] = {
#define COLOR(color)   { Color_ ## color, #color }
  COLOR(Red), COLOR(Green), COLOR(Blue)
};

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:

#define ADD_M(a, b) ((a) + (b))
static inline int add_f(int a, int b) {
  return a + b;
}

In this example, the ADD_M(3,4) macro invocation yields a constant expression, 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.

Risk Assessment

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

ToolVersionCheckerDescription
Astrée
19.04
macro-function-likeFully checked
Axivion Bauhaus Suite

6.9.0

CertC-PRE00

ECLAIR

1.2

CC2.PRE00

Fully implemented

Klocwork
2018
MISRA.DEFINE.FUNC
LDRA tool suite
9.7.1

340 S

Enhanced enforcement

Parasoft C/C++test
10.4.2
CERT_C-PRE00-a

A function should be used in preference to a function-like macro

Polyspace Bug Finder

R2019b

CERT C: Rec. PRE00-CChecks for use of function-like macro instead of function (rec. fully covered)


PRQA QA-C
9.5
3453Fully implemented
RuleChecker
19.04
macro-function-likeFully checked
SonarQube C/C++ Plugin
3.11
S960

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

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



13 Comments

  1. C99, section 6.5.15 "The Conditional Operator", says:

    The first operand is evaluated; there is a sequence point after its evaluation. The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.

    That seems to suggest that PRE00-EX2 is unnecessary, since you don't need a macro to use 'lazy evaluation' with the conditional operator.

    1. I'm also not sure I understand the point of PRE00-EX2. Is there a better example?

      In my experience, the two most compelling use cases in favor of macros are:

      • token concatenation and/or stringification
        enum Color { Color_Red, Color_Green, Color_Blue };
        static const struct {
          enum Color  color;
          const char *name;
        } colors[] = {
        #define COLOR(color)   { Color_ ## color, #color }
          COLOR(Red), COLOR(Green), COLOR(Blue)
        };
        
      • implicit expansion of __FILE__, __LINE__, and __func__
        #ifndef NDEBUG
        #  define TRACE(fmt, ...) \
            trace("%s:%d (%s): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__)
        #else
        #  define TRACE(...) (void)0
        #endif
        
        void trace(const char *fmt, ...);
        
        void foo(int a, const char *b) {
          TRACE("a = %d, b = %s\n", a, b);
          /* ... */
        }
        
      1. I replaced the existing exception with your token concatenation and/or stringification example.

        I hesitated for your second example which uses implicit expansion of _FILE, __LINE, and __func_ but also defines a function like macro TRACE().

        Doesn't this represent yet another exception to this guideline?

        1. Yes. Specifically the _FILE_ and _LINE_ macros (and others?) provide info you can't get properly from embedding them in a function.

          1. I missed your and Robert's responses to my comment.

            Yes, my goal behind showing the TRACE() macro was to illustrate an exception to this guideline. However, I'm not sure I see the relationship between __FILE__, __LINE__ and this guideline since it focuses on function-like macros. That said, I think a guideline advising against defining macros in general, i.e., including object-like macros, would be appropriate, but I cannot find one. If no one objects I'd like to go ahead and add one. Or rather, extend this one to both kinds of macros.

            1. If you have a good 'avoid writing object-like macros' go ahead & suggest it. This rule is big enough and covers the function-like- macro space; I'd ranter not complicate it further.

  2. The return type in PRE00-EX3 appears to be missing.

  3. Are there any objections to extending this guideline to recommend against all macros, not just function-like macros, and renaming it to: Prefer typed constants and functions to macros?

    The rationale should be obvious: object-like macros are subject to type safety problems, cause namespace pollution, and may cause code bloat (due to duplication of string literals, for example).

    1. We have a number of guidelines which when taken together say this.

      DCL06-C isn't as strong as "don't use object-like macros" and in fact contains a compliant solution using object-like macros.

      I'm a little worried that such a guideline might be too strong for C language. Tom frequently comments at WG14 meetings that "we're not C+, we like macros" which I think may represent a slightly different mindset among C programmers than C+ programmers. If someone would to write a checker for such a guideline it would definitely find alot of true positives (as would a checker for finding function-like macros). It might be that you could just strengthen DCL06-C to suggest a preference order among the various mechanisms defining symbolic constants and clearly identify the problems with object-like macros. I think there are good arguments for using enum constants over const-qualified Objects for integer constants as well (don't consume memory, you can't take their address).

      1. I'm actually with Tom on this one. Despite everyone's best efforts, the unfortunate reality is that writing portable programs in C and especially in C++ would be virtually impossible without macros and I suspect a checker would have plenty of macros of both kinds to complain about. (Google Code Search returns about 41,000 records of object-like macros in C code and 30,000 occurrences of function-like macros).

        That being said, it seems to me that a function-like macro that avoids the common pitfalls (i.e., PRE01-C, PRE02-C, PRE31-C, etc.) is no more dangerous than an object-like macro. Both run the risk of colliding with names in other scopes, but with care, both can be used safely. Given that, the rationale for having a guideline advising against writing one kind seems just as good for having a guideline against the other.

        Furthermore, as evidenced by the number of exceptions to this guideline, there are a good number of use cases involving function-like macros that cannot be adequately handled by other means (inline functions). On the other hand, outside of #if directives I cannot think of any use cases involving object-like macros that could not be equivalently handled by using static constants of the appropriate type instead. In my view, this makes the argument against object-like macros even more compelling than the one against function-like ones.

  4. inline is actually wrong. It's the C99 keyword that actually means the definition in header file is provided for inlining (but the compiler doesn't have to inline it, in fact some compilers ignore this keyword entirely). Inline functions are relatively tricky in C99 - the inline keyword should be specified in the header, but the actual code also should have definition of it without the keyword (code duplication, but I doubt anyone cares). Or use static inline to actually make function inline (without actually compiling it to object code).

    This is not some random issue, forgetting that inline used by itself doesn't actually define the function causes problems in non-gcc compilers (like clang).

    1. The usage of inline in this recommendation is acceptable because the examples place the inline on the function definition, not the function prototype.  All of the examples show functions with external linkage due to being at file scope, but also demonstrate usages within the same translation unit.  As such, they are a valid inline definition. I am not certain what problems you are seeing with compilers such as Clang, but my testing of these examples demonstrated correct behavior (so perhaps it's a version-specific bug with the compiler, or perhaps your tests differ from mine).