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
24.04

macro-function-like
function-like-macro-expansion

Fully checked
Axivion Bauhaus Suite

7.2.0

CertC-PRE00
CodeSonar
8.1p0

LANG.PREPROC.FUNCMACRO

Function-Like Macro

ECLAIR

1.2

CC2.PRE00

Fully implemented

Helix QAC

2024.1

C3453
Klocwork
2024.1
MISRA.DEFINE.FUNC
LDRA tool suite
9.7.1

340 S

Enhanced enforcement

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

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

PC-lint Plus

1.4

9026

Assistance provided

Polyspace Bug Finder

R2023b

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


RuleChecker
24.04
macro-function-like
function-like-macro-expansion
Fully 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



23 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).

        1. inline always misused

  5. I think it is also worth pointing out some of the extremely subtle mistakes one can make when porting macros to functions.
    For instance, consider the following (admittedly contrived) noncompliant code example.
    In this example, the macro INIT_DB applies the unary address-of operator (&) to its argument con:

    struct connection_t {
        // name, socket, etc.
    };
    
    struct database_t {
        struct connection_t *conn;
    };
    
    #define INIT_DB(db, con) ((db)->conn = &(con))
    
    void func() {
        struct database_t *db = malloc(sizeof(struct database_t));
        struct connection_t con;
        INIT_DB(db, con);
    	free(db);
    }

    An obvious but erroneous way to make this example compliant would be to replace the macro INIT_DB with a static inline function:

    static inline void init_db(struct database_t *db, struct connection_t con) {
      ((db)->conn = &(con));
    }

    However this would not preserve the behavior of the original macro, because of the difference between function semantics (call-by-value) and macro semantics (call-by-name).
    The macro invocation, once expanded, applies the & operator to the local variable con declared in the definition of func.
    On the other hand, the function definition of init_db applies the & operator to the function parameter con instead.
    So if one were to replace the macro INIT_DB with the function init_db, they would assign db's conn member to a dangling pointer.

    The compliant solution would be to replace the macro definition with a slightly different function definition:

    static inline void init_db(struct database_t *db, struct connection_t *con) {
      ((db)->conn = con);
    }

    and also change the invocation site to match:

    INIT_DB(db, &con);


    This may even warrant its own recommendation (e.g., "Do not use argument L-values in macro definitions"), though I am not sure.

    1. Brent:

      In your code example, INIT_DB actually violates DCL30-C, by letting the static db struct contain a pointer to the con struct, which is local to func(). This is OK by itself, but if your code ever tries to dereference db->con after exiting func(), you will be in for a surprise.  Changing INIT_DB to an inline function does nothing to address this problem (although it doesn't hurt things worse).

      See CERT rule DCL30-C, and particularly, its first non-compliant code example for details.

      1. Hi David,

        Thanks for catching that violation of DCL30-C.
        I've edited my original comment to be in compliance with this rule.

        This has got me thinking about PRE00-C-EX5.
        If a macro reads the L-value of one of its arguments, does it fall under this exception, and thus should not be converted to an inline function?
        Here are some macros I found in Lua, Linux, and Perl that do this:

        - https://github.com/lua/lua/blob/26be27459b11feabed52cf40aaa76f86c7edc977/lundump.c#L58
        - https://github.com/torvalds/linux/blob/49da070062390094112b423ba443ea193527b2e4/fs/stat.c#L364
        - https://github.com/Perl/perl5/blob/f08b7a33d99b9b204260e108bc1789c5392aff36/numeric.c#L1461

        Would these all be examples of PRE00-C-EX5?
        In either case I think that exception may benefit from an example, would you consider adding one?

        1. If a macro reads the L-value of one of its arguments, does it fall under this exception, and thus should not be converted to an inline function?

          If it doesn't, then we should make a new exception for it. Consider this macro:

          #define ALL_ONES(x) memset(&(x), '\xff', sizeof(x))

          It can't be replaced with an inline function, since then sizeof(x) would end up being the size of a pointer instead of the size of the thing it's supposed to be operating on, so we definitely shouldn't ban it as a macro.

          1. Brent & Joe:

            The Linux code example cited by Brent, and Joe's code example both use macros to achieve type genericity; that' is why they don't translate well to inline functions. They would be captured by PRE00-C:EX4 rather than EX5.

            Typically we enumerate exceptions for rules, not recommendations. We  enumerated the exceptions for this recommendation in the hope of promoting it to a rule; however, I doubt we can promote this to a rule yet. I do suspect there are many more exceptions that we haven't enumerated.


            1. Hi David,

              Good point about PRE00-C-EX4 - I had not considered that these macros would be covered under that exception. What may also be relevant is that macros share their caller's scope, whereas functions create their own scope, causing l-value differences:

              #define M(x) (&(x))
              int *f(int x) { return (&(x)); }
              

              M doesn't return the same L-value as f, because M has no local scope. I originally thought this was due to call-by-name vs. call-by-value, but similar scoping anomalies happen without parameters too:

              #define X() g
              &X();  // gets address of g
              

              Do you think such scoping differences warrant a separate exception as Joseph hinted at?

              P.S. I'm a PhD student working on automatically transforming macro functions to C functions, so these guidelines are an interesting source for deciding when a macro should or shouldn't be transformed.

              1. Brent:

                Interesting. I am also working on automated source code transformations.  It is possible that in the course of your studies you will come to understand EX5 better than us; in which case you can advise us on how to improve that exception :)

                I suspect that the answer to Joe's question is no: that EX5 would cover a macro that needs the l-value of its argument.  Unless the inline function took a pointer to the argument.  It's hard to tell...can you come up with a code example that might fall under EX5 but not EX4?  I do think that EX5 needs a code example to support it.

                1. Hi David,

                  Here is an example of a macro that falls under EX5 and does not fall under EX4 (I think):

                  #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap)
                  

                  I do not think this macro is generic because cap has to have the same type as &tterm->Strings[n] for the expression to typecheck without warning.

                  Here are two more examples of realworld macros that fall under EX5 and not EX5, as well as three contrived examples.

                  Hopefully this will be useful for the standard, and perhaps we can chat in the future about source-to-source transformations.

                  1. Brent:

                    I agree your code examples are not type-generic, so EX4 does not apply to them.
                    But I'm also not convinced they are better off as macros rather than inline functions:

                    inline bool same_cap(term_type *tterm, size_t n,  cap_type *cap) {
                       return tterm->Strings[n]  == cap;
                    }

                    if (same_cap( tterm, n, &cap))
                      ...

                    The inline function has the advantage of being more "honest" about the number & types of arguments.