(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
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 ;Forgetting to wrap variables in parentheses
#define TWICE(x) 2 * x int M = TWICE(3 + 5)This expands to
M = 2 * 3 + 5, giving11instead of the expected16. 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:
- Using the
inlinekeyword — the compiler expands the function at compile time. - 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
| Function | Macro | Inline | |
|---|---|---|---|
| Execution Time | Runtime | Preprocessing | Compiling |
| Usage | type function (arguments) | #define | inline |
| Declaration Position | Anywhere | Must be declared at the top | Inside or outside a class |
| Termination | Closing brace } | Newline (use \ for multi-line) | Closing brace } |
| Mechanism | push / pop | text substitution | function substitution |
| Debugging | Easy | Difficult | Easy |
| Automation | No | Must be explicitly defined | Short class functions may be auto-inlined by the compiler |
| Expansion | No | Always | Can be disabled with compiler flags |
| Speed | Slower | Faster | Faster |
| Space Usage | Smaller | Larger | Larger |
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.