The Preprocessor: Function-Like, Variadic, Stringification, Pasting
This chapter covers function-like macros, variadic macros, and the two special operators # (stringification) and ## (token pasting). These are powerful but error-prone; they must be used with precise parenthesization.
Why This Matters
Function-like macros can eliminate function-call overhead and generate code, but their naive use causes double-evaluation bugs and precedence errors. The # and ## operators are the basis of logging macros and code generation.
Prerequisites
c.core.36— object-like macros and#include.
Core Concept
Function-like macros
A function-like macro has a parameter list:
#define SQUARE(x) ((x) * (x))
Used as SQUARE(5) → ((5) * (5)). Note the double parentheses: one around each parameter, one around the whole expression. This is essential to avoid precedence and double-evaluation bugs.
Double evaluation
SQUARE(i++) expands to ((i++) * (i++)), which increments i twice and is undefined behavior. Function-like macros do not evaluate arguments once like functions do.
Variadic macros (C99)
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
__VA_ARGS__ expands to the trailing arguments. The ##__VA_ARGS__ extension (GCC/Clang) removes the preceding comma when no variadic arguments are given, but it is non-standard.
Stringification (#)
#param turns the argument into a string literal:
#define STR(x) #x
STR(hello) /* "hello" */
Token pasting (##)
a ## b concatenates tokens a and b into a single token:
#define CONCAT(a, b) a ## b
CONCAT(foo, bar) /* foobar */
Syntax
#define NAME(params) replacement
#define NAME(...) replacement /* variadic */
#define NAME(params, ...) replacement
#param
tok1 ## tok2
Examples
Safe function-like macro
#define MAX(a, b) ((a) > (b) ? (a) : (b))
Logging with __FILE__ and __LINE__
#include <stdio.h>
#define LOG_MSG(msg) \
printf("%s:%d: %s\n", __FILE__, __LINE__, msg)
int main(void)
{
LOG_MSG("something happened");
return 0;
}
Stringification and token pasting
#include <stdio.h>
#define STR(x) #x
#define GLUE(a, b) a ## b
int main(void)
{
int foobar = 42;
printf("%s\n", STR(hello world)); /* "hello world" */
printf("%d\n", GLUE(foo, bar)); /* foobar -> 42 */
return 0;
}
Expected output:
hello world
42
How It Works
The preprocessor substitutes arguments into the replacement list, then applies # and ## (before ordinary macro expansion, in a specific order). The result is re-scanned for further macro expansion. This rescanning can produce surprising results, especially with ##.
Variations
Variadic macro comma handling
Standard C requires at least one variadic argument when ... is used. The ,##__VA_ARGS__ GNU extension allows zero. For portable code, provide a default or use a different design.
Recursive macros (not allowed)
Macros do not expand recursively. A macro that references itself during its own expansion is not expanded again, which prevents infinite recursion.
Common Mistakes
- Forgetting parentheses (precedence bugs).
- Using a side-effecting argument in a macro (double evaluation, UB).
- Assuming macros behave like functions (they do not).
- Using
##where the result is not a valid token.
Undefined Behavior
- A macro that produces an expression with unsequenced side effects on the same
object (e.g., SQUARE(i++)) is UB. VERIFIED
- Producing a token that is not a valid preprocessing token with
##is a
constraint violation.
Portability
- Variadic macros are C99 and later.
,##__VA_ARGS__is a GCC/Clang extension, not ISO C.#and##are standard.
Under the Hood
All macro expansion happens in the preprocessor, before the compiler sees the code. There is no run-time cost and no type checking. The compiler only sees the expanded tokens.
Practical Usage
- Use function-like macros for small, performance-critical operations where a
function call is undesirable and side-effect arguments are avoided.
- Use
#for logging/assertion messages. - Use
##for generating identifiers in code-generation macros. - Prefer
static inlinefunctions over macros when type checking and single
evaluation matter.
Exercises
1. Write a MAX macro and demonstrate a precedence bug, then fix it with parentheses. 2. Show the double-evaluation bug with SQUARE(i++) and explain the UB. 3. Write a logging macro using # and __LINE__. 4. Use ## to generate unique variable names in a macro.
Deep Challenge
Write a macro SWAP(a, b) that swaps two variables of the same type *without* double-evaluating their arguments. Explain why this is difficult or impossible with a pure function-like macro, and propose a static inline or _Generic alternative that is safe.
Related Concepts
c.func.inline— inline functions vs. macros.c.pp.conditional— conditional compilation.c.func.variadic— variadic functions vs. macros.
References
- ISO/IEC 9899:2018 §6.10.3 (macro replacement), §6.10.3.2 (#), §6.10.3.3
(##).
Verification
- Double-evaluation of side-effecting macro args can be UB.
VERIFIED - Variadic macros are C99.
VERIFIED ##__VA_ARGS__is a GNU extension.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Function-like macros
- [ ] Double evaluation
- [ ] Variadic macros
- [ ] Stringification (#)
- [ ] Token pasting (##)
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.pp.fn-macro | 0 | 6 |
| c.pp.stringify | 0 | 5 |
| c.pp.paste | 0 | 5 |
| c.pp.variadic-macro | 0 | 5 |