Macros & Inline Functions
Macros & Inline Functions
Macros are one of the most powerful — and most dangerous — features of the C preprocessor. Understanding both their capabilities and their pitfalls is crucial for reading and writing real-world C code.
Object-like Macros
The simplest kind replaces a name with a value. By convention macro names are written in ALL_CAPS:
#define PI 3.14159265358979323846
#define MAX_NODES 1024
#define NEWLINE '\n'Unlike const variables, object-like macros have no type, no storage, and no scope — they are pure text substitution. The compiler never sees the macro name, only the replacement text.
Function-like Macros
A macro can take arguments, mimicking a function:
#define SQUARE(x) x * x /* DANGEROUS — do not use */
#define SQUARE(x) ((x) * (x)) /* safer */The Two Great Macro Pitfalls
Operator precedence: SQUARE(1 + 2) with the unsafe definition expands to 1 + 2 * 1 + 2 = 5, not 9. Always wrap both the entire expression and each parameter in parentheses.
Side effects: SQUARE(i++) expands to ((i++) * (i++)) — i is incremented twice. Never pass expressions with side effects to function-like macros.
Stringification (#)
Placing # before a macro parameter converts it to a string literal:
#define STRINGIFY(x) #x
STRINGIFY(hello world) /* becomes "hello world" */Useful for printing expressions in debug macros.
Token Pasting (##)
The ## operator concatenates two tokens into one:
#define MAKE_VAR(n) var_##n
MAKE_VAR(speed) /* becomes var_speed */This is used in code generation patterns where you build identifiers programmatically.
Variadic Macros (__VA_ARGS__)
C99 introduced variadic macros to accept a variable number of arguments:
#define PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)The ## before __VA_ARGS__ is a GCC extension that removes the preceding comma when no variadic arguments are passed, avoiding a syntax error.
inline Functions — The Safe Alternative
C99 introduced the inline keyword. An inline function has proper type checking, respects scope, evaluates arguments exactly once, and is a hint to the compiler to expand the call in place (avoiding function call overhead):
static inline int square(int x) { return x * x; }For most purposes where you would use a function-like macro, an inline function is safer and equally efficient. Reserve macros for situations that genuinely require text substitution — like code generation with ## or wrapping __LINE__/__FILE__ capture.
When to Use Each
| Situation | Use |
|---|---|
| Numeric constant | #define or const |
| Type-generic operation | Function-like macro |
| Performance-critical small function | static inline |
| Building identifiers at compile time | ## macro |
| Debug assert with source location | Macro (needs __LINE__) |
Code Examples
#include <stdio.h>
/* DANGEROUS: missing parentheses */
#define BAD_SQUARE(x) x * x
#define BAD_DOUBLE(x) x + x
/* Safe: every parameter and the whole expression are parenthesized */
#define SAFE_SQUARE(x) ((x) * (x))
#define SAFE_DOUBLE(x) ((x) + (x))
int main(void) {
int a = 3;
/* Precedence trap */
printf("BAD_SQUARE(1+2) = %d\n", BAD_SQUARE(1+2)); /* 1+2*1+2 = 5, not 9 */
printf("SAFE_SQUARE(1+2) = %d\n", SAFE_SQUARE(1+2)); /* ((1+2)*(1+2)) = 9 */
/* Division trap: 10 / BAD_DOUBLE(5) -> 10 / 5 + 5 = 7, not 1 */
printf("10 / BAD_DOUBLE(5) = %d\n", 10 / BAD_DOUBLE(5));
printf("10 / SAFE_DOUBLE(5) = %d\n", 10 / SAFE_DOUBLE(5));
/* Side-effect trap */
int i = 3;
int result = SAFE_SQUARE(i++); /* i++ evaluated TWICE — undefined behaviour */
printf("i after SAFE_SQUARE(i++) = %d (i was incremented twice!)\n", i);
(void)result;
return 0;
}BAD_SQUARE(1+2) expands to 1+2*1+2 because * binds tighter than +. The safe version wraps parameters so the expression is ((1+2)*(1+2)). The side-effect trap shows why you should never pass i++ to a macro — each occurrence of x in the expansion increments i separately.
#include <stdio.h>
/* Stringification: # turns a token into a string literal */
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x) /* double expansion trick for macros as args */
/* Token pasting: ## concatenates two tokens */
#define MAKE_GETTER(type, field) \
type get_##field(void) { return g_##field; }
/* Variadic debug macro using __VA_ARGS__ */
#define DEBUG_PRINT(fmt, ...) \
printf("[%s:%d] " fmt "\n", __func__, __LINE__, ##__VA_ARGS__)
/* Global variables for the generated getters */
static int g_speed = 120;
static char g_name[] = "engine";
/* Token pasting generates these two functions: */
MAKE_GETTER(int, speed) /* int get_speed(void) { return g_speed; } */
MAKE_GETTER(char*, name) /* char* get_name(void) { return g_name; } */
#define VERSION_MAJOR 2
#define VERSION_MINOR 7
#define VERSION_STR TOSTRING(VERSION_MAJOR) "." TOSTRING(VERSION_MINOR)
int main(void) {
printf("Stringify: %s\n", STRINGIFY(hello world));
printf("Version : %s\n", VERSION_STR);
printf("Speed : %d\n", get_speed());
printf("Name : %s\n", get_name());
DEBUG_PRINT("All systems nominal, speed=%d", get_speed());
return 0;
}STRINGIFY converts a token to a string. The double-expansion trick (TOSTRING calling STRINGIFY) is needed when the argument is itself a macro — without it, the macro name rather than its value would be stringified. MAKE_GETTER uses ## to build two different function names and variable names from a single macro invocation.
#include <stdio.h>
#include <math.h>
/* Old-style macro — no type safety, side-effect prone */
#define MACRO_MAX(a, b) ((a) > (b) ? (a) : (b))
/* C99 inline function — type safe, evaluates args once */
static inline int imax(int a, int b) {
return a > b ? a : b;
}
static inline double dmax(double a, double b) {
return a > b ? a : b;
}
/* Inline functions with __LINE__/__FILE__ still possible via wrapper macro */
#define ASSERT(cond) \
do { \
if (!(cond)) { \
fprintf(stderr, "Assertion failed: %s\n at %s:%d\n", \
#cond, __FILE__, __LINE__); \
} \
} while (0)
static inline double hypotenuse(double a, double b) {
return sqrt(a * a + b * b);
}
int main(void) {
int x = 5, y = 10;
/* inline: y++ evaluated exactly once */
printf("imax(%d, y++) = %d, y is now %d\n", x, imax(x, y++), y);
int i = 5, j = 10;
/* macro: j++ would be evaluated twice if j > i */
printf("MACRO_MAX(%d, j++) = %d, j is now %d\n", i, MACRO_MAX(i, j++), j);
printf("hypotenuse(3,4) = %.1f\n", hypotenuse(3.0, 4.0));
printf("dmax(2.5, 3.7) = %.1f\n", dmax(2.5, 3.7));
ASSERT(x < y); /* passes silently */
ASSERT(x > 100); /* prints assertion failure message */
return 0;
}The inline imax increments y exactly once. The MACRO_MAX increments j twice because the macro expands j++ in both the comparison and the result. This is the decisive reason to prefer inline functions. The do { } while(0) wrapper in ASSERT lets you use it safely in if/else chains without needing braces.
Quick Quiz
1. Why should every parameter of a function-like macro be wrapped in parentheses in the replacement text?
2. What does the # operator do inside a macro definition?
3. What is the main advantage of a static inline function over a function-like macro?
Was this lesson helpful?