(the post is automatically translated by AI)

Introduction

Macros and inline functions are two techniques for code reuse and expansion. Unlike regular function calls, both avoid the overhead of subroutine push/pop operations at runtime, which can speed up execution.

The key difference between them is when the expansion happens: macros are substituted by the preprocessor before compilation, while inline functions are expanded by the compiler during compilation. However, both result in larger binary sizes compared to using regular functions, since the code is duplicated at every call site.

In this article, we’ll explain the principles behind macros and inline functions, and compare them with practical examples.

Macro

A macro is expanded by the preprocessor through text substitution. As mentioned in the article #: The Language of the Preprocessor, the #define directive lets us define function-like macros 1.

A quick refresher on macro syntax:

#define identifier(parameters, ...) replacement-list

Every occurrence of identifier in the code is replaced by replacement-list at the preprocessing stage. A simple example:

#define MAX(x, y) (x) > (y) ? (x) : (y)

After the preprocessor runs, every MAX(x, y) is replaced with (x) > (y) ? (x) : (y).

Common Pitfalls

  1. Accidentally adding = or ;

    #define MAX(x, y) (x) > (y) ? (x) : (y)   // correct
    #define MAX(x, y) = (x) > (y) ? (x) : (y) // error 1(a): stray =
    #define MAX(x, y) (x) > (y) ? (x) : (y);  // error 1(b): stray ;
    
  2. Forgetting to wrap variables in parentheses

    #define TWICE(x) 2 * x
    
    int M = TWICE(3 + 5)
    

    This expands to M = 2 * 3 + 5, giving 11 instead of the expected 16. Always protect operands with parentheses.

Inline Function

An inline function (内嵌函式) embeds the function body directly at the call site. There are two ways to use it:

  1. Using the inline keyword — the compiler expands the function at compile time.
  2. Compiler auto-inlining — the compiler may automatically inline short functions for performance.

Example using the inline keyword:

inline int max(x, y) {
    if (x >= y) return x;
    else return y;
}

Observing Macro and Inline with GCC

Observing Macros

Use the -E flag to see what the preprocessor produces. Given:

/* test_macro_01.c */
#define MAX(x, y) x > y? x: y

int main () {
    MAX(2, 3);
}

After gcc -E -o test_macro.i test_macro.c:

/* test_macro_01.i */
...

int main()
{
    2 > 3 ? 2 : 3;
}

If we use a post-increment operator:

/* test_macro_02.c */
#define MAX(x, y) x > y? x: y

int main () {
    int i=4, j=5;
    MAX(i++, j);
}

The expanded result:

/* test_macro_02.i */
...

int main()
{
    int i=4, j=5;
    i++ > j ? i++ : j;
}

Because the macro is plain text substitution, i is incremented twice, ending up as 6 instead of 5.

Observing Inline

Use gcc -S to view the generated assembly. Comparing a function with and without inline:

/* test_inline.c */
inline int twice (int x) {
    return 2 * x;
}
int main () {
    twice(3);
}
/* test_no_inline.c */
int twice (int x) {
    return 2 * x;
}
int main () {
    twice (3);
}

From the assembly output, the inline version merges twice directly into _main, eliminating the extra push/pop overhead. The non-inline version keeps a separate _twice subroutine that is called via callq.

Macro vs Inline Function

FunctionMacroInline
Execution TimeRuntimePreprocessingCompiling
Usagetype function (arguments)#defineinline
Declaration PositionAnywhereMust be declared at the topInside or outside a class
TerminationClosing brace }Newline (use \ for multi-line)Closing brace }
Mechanismpush / poptext substitutionfunction substitution
DebuggingEasyDifficultEasy
AutomationNoMust be explicitly definedShort class functions may be auto-inlined by the compiler
ExpansionNoAlwaysCan be disabled with compiler flags
SpeedSlowerFasterFaster
Space UsageSmallerLargerLarger

Conclusion

In this article, we introduced two code reuse techniques beyond regular functions: macros and inline functions. We gave examples of how each works and common pitfalls (such as the macro definition errors 2). We then used GCC 3 to observe each at a different compilation stage and compared them in the table above.

If you’re curious about preprocessors in general, check out the earlier article #: The Language of the Preprocessor.

References