C Part I · Module 11 · Building Multi-File C Programs
Object-Like and Function-Like Macros
One stage of the build obeys none of the rules in this module. The preprocessor does not know about scope, does not know about types, and has never heard of linkage — it replaces tokens with other tokens and passes the result on. That is enough to be genuinely useful and enough to produce failures that look nothing like ordinary C bugs, because the source reads correctly and only the expansion shows why it is wrong. This chapter covers both kinds of macro and the three things that make them misbehave: substitution happens on tokens rather than characters, a result without parentheses can be torn apart by whatever surrounds it, and an argument mentioned twice in the replacement is evaluated twice. Every trap below is shown as a pair — the wrong answer, and the expansion that caused it.
Intermediate20 min readCPart IFundamentalsBuild
Part I · Module 11 · Chapter 11.6 · Object-Like and Function-Like Macros
1. The Two Kinds
#define LIMIT 10 /* object-like */
#define TWICE(x) ((x) + (x)) /* function-like */An object-like macro replaces a name with a sequence of tokens. A function-like macro takes arguments and substitutes them into the replacement.
The distinction is made by one character: whether ( immediately follows the macro name in the #define.
#define BAD (x) ((x) + (x)) /* object-like! replacement is "(x) ((x) + (x))" */A space before the parenthesis makes it object-like, and the result is nonsense that often compiles. There is no space in a function-like definition, ever.
Both are handled in preprocessing — Chapter 11.2's phase 1 — before the compiler sees anything. The result of a macro has no type, no scope, and no linkage, because by the time those exist the macro is gone.
2. Tokens, Not Characters
This is the first of the chapter's three obligations and the one that prevents a whole class of wrong expectations.
#include <stdio.h>
#define SIZE 10
int main(void)
{
int SIZE_LIMIT = 1; /* NOT replaced: SIZE_LIMIT is one token */
int arr[SIZE];
const char *s = "SIZE"; /* NOT replaced: inside a string literal */
printf("SIZE=%d SIZE_LIMIT=%d sizeof arr=%zu s=\"%s\"\n",
SIZE, SIZE_LIMIT, sizeof arr, s);
return 0;
}SIZE=10 SIZE_LIMIT=1 sizeof arr=40 s="SIZE"And the expansion:
int SIZE_LIMIT = 1;
const char *s = "SIZE";
printf("SIZE=%d SIZE_LIMIT=%d sizeof arr=%zu s=\"%s\"\n",
10, SIZE_LIMIT, sizeof arr, s);The preprocessor works on tokens.
SIZE_LIMITis a single identifier token that happens to begin with the lettersSIZE, so it is not touched. The contents of a string literal are one token, so"SIZE"is not touched. Only the standaloneSIZEtoken became10.
Two more consequences of the same rule.
A macro does not expand inside itself. #define X (X + 1) terminates — during the expansion of X, the name X is not replaced again — so recursion is impossible and the common fear of infinite expansion is unfounded.
A macro has no scope. It is in effect from its #define to the matching #undef, or to the end of the translation unit. It ignores braces, functions and files, which is why Chapter 11.5's three properties do not apply to it and why macro names are conventionally SHOUTED — the capitals are the only warning a reader gets.
3. The Missing-Parentheses Trap
The second obligation, and it has two levels. Most treatments show the first and stop.
#define SQ_BAD(x) x * x /* no parentheses anywhere */
#define SQ_HALF(x) (x) * (x) /* arguments wrapped, result not */
#define SQ_OK(x) ((x) * (x)) /* both */SQ_BAD(3) = 9
SQ_BAD(1+2) = 5 (expected 9)
SQ_HALF(1+2) = 9 (expected 9)
100/SQ_HALF(2) = 100 (expected 25)
100/SQ_OK(2) = 25 (expected 25)And the expansions that produced them:
3 * 3
1+2 * 1+2 /* SQ_BAD(1+2) -> 1 + 2 + 2 = 5 */
(1+2) * (1+2) /* SQ_HALF(1+2) -> 9, fixed */
100/(2) * (2) /* 100/SQ_HALF(2) -> 50 * 2 = 100 */
100/((2) * (2)) /* 100/SQ_OK(2) -> 25, fixed */Level one — the argument. SQ_BAD(1+2) became 1+2 * 1+2. The text of the argument was substituted as-is, and * bound tighter than +. Wrapping each parameter fixes it.
Level two — the result. SQ_HALF wraps its arguments and is still wrong: 100/SQ_HALF(2) became 100/(2) * (2), which divides and then multiplies, giving 100 instead of 25. The macro's result has to survive whatever surrounds it, and only outer parentheses guarantee that.
The same applies to object-like macros:
1/HALF_BAD = 0.5 (expected 2) expansion: 1/1.0/2.0
1/HALF_OK = 2 expansion: 1/(1.0/2.0)Parenthesise every parameter and the whole replacement.
#define SQ(x) ((x) * (x)). Both, every time — the arguments because of what is inside them, the outer pair because of what is outside the macro.
The one exception: if the replacement is a single token (#define LIMIT 10) there is nothing to tear apart, and ((10)) adds only noise.
4. The Double-Evaluation Trap
The third obligation, and the one that produces wrong behaviour rather than a wrong value.
#define MAX(a,b) ((a) > (b) ? (a) : (b))Correctly parenthesised, by §3's rule, and still a hazard — because a appears twice in the replacement.
#include <stdio.h>
#define MAX(a,b) ((a) > (b) ? (a) : (b))
static int calls = 0;
static int reading(void) { calls++; return 50; }
int main(void)
{
int i = 9, j = 5;
int m = MAX(i++, j); /* the TRUE branch is taken */
printf("MAX(i++, j) with i=9, j=5: m=%d i=%d\n", m, i);
calls = 0;
int n = MAX(reading(), 15);
printf("MAX(reading(), 15): n=%d reading() was called %d time(s)\n", n, calls);
return 0;
}MAX(i++, j) with i=9, j=5: m=10 i=11
MAX(reading(), 15): n=50 reading() was called 2 time(s) int m = ((i++) > (j) ? (i++) : (j));
int n = ((reading()) > (15) ? (reading()) : (15));i started at 9. One increment would leave it at 10. It is 11 — the macro incremented it twice — and m is 10 rather than the 9 a function would have returned. reading() ran twice, which would be a bug the moment it read a hardware register, consumed an input, or cost anything.
Two points of precision.
This is not undefined behaviour. The conditional operator has a sequence point between the condition and the selected branch (C17 §6.5.15), so the two increments are properly ordered. It is a defined wrong answer, which is worse than a crash for all the reasons Chapter 9.12 §4 gave.
It only fires when the duplicated operand is selected. With i = 1 and j = 5 the false branch is taken, i++ runs once, and the output looks innocent. A macro like this can pass every test and fail on the day the other branch is chosen — the same shape as Chapter 9.7's realloc bug.
5. The Statement Trap
A macro that expands to more than one statement does not become one statement.
#define LOG_BAD(x) printf("a=%d\n", x); printf("b=%d\n", x)
#define LOG_OK(x) do { printf("a=%d\n", x); printf("b=%d\n", x); } while (0)
if (flag)
LOG_BAD(1);
printf("--- after LOG_BAD ---\n");
if (flag)
LOG_OK(2);
printf("--- after LOG_OK ---\n");b=1
--- after LOG_BAD ---
--- after LOG_OK ---b=1 printed, with flag set to zero. Only the first printf attached to the if; the second became an unconditional statement following it. LOG_OK printed nothing, correctly.
No diagnostic was issued at -Wall -Wextra -Wpedantic.
Wrap a multi-statement macro in
do { ... } while (0). It makes the body one statement, it works in every position anifbranch can occupy, and it still requires a trailing semicolon at the call site — soLOG_OK(2);reads exactly like a function call.
Braces alone are not enough: { ... } followed by the caller's ; leaves an empty statement, which breaks if (c) MACRO(x); else .... The do/while (0) wrapper is the idiom precisely because it consumes the semicolon.
6. Stringizing and Token Pasting
Two operators exist only inside function-like macros.
# turns an argument into a string literal.
#define SHOW(expr) printf(#expr " = %d\n", (expr))
SHOW(a + b);
SHOW(a * b + 1);a + b = 7
a * b + 1 = 13 printf("a + b" " = %d\n", (a + b));
printf("a * b + 1" " = %d\n", (a * b + 1));The argument's text became a literal, and adjacent string literals are concatenated — one of Chapter 11.2's later translation phases. This is how assertion and debug macros print the expression that failed, and it is something no function can do.
## joins two tokens into one.
#define FIELD(n) field_##n
printf("FIELD(1)=%d FIELD(2)=%d\n", FIELD(1), FIELD(2));FIELD(1)=11 FIELD(2)=22 printf("FIELD(1)=%d FIELD(2)=%d\n", field_1, field_2);field_ and 1 were pasted into the single identifier field_1. Use it sparingly: pasted names are invisible to search, which is a real cost in a codebase you have to navigate.
7. Predefined Macros, and One That Is Not
printf("file %s line %d func %s\n", __FILE__, __LINE__, __func__);file m7.c line 11 func main printf("file %s line %d func %s\n", "m7.c", 11, __func__);Look carefully at that expansion. __FILE__ became "m7.c" and __LINE__ became 11 — and __func__ is still there.
__FILE__and__LINE__are macros;__func__is not.__func__is a predefined identifier (C17 §6.4.2.2) — the compiler declares it inside every function as a static array of characters holding that function's name. The preprocessor has never heard of it, which is exactly why it survives into the compiler's input.
The practical consequence: #ifdef __func__ is false, and __func__ cannot be stringized or pasted, because # and ## operate on preprocessing tokens and __func__ is not one the preprocessor treats specially.
Chapter 11.2 §2 measured 480 predefined macros on this target. The ones worth knowing now are __FILE__, __LINE__, __DATE__, __TIME__, __STDC__ and __STDC_VERSION__ — the last of which tells you which standard a build is actually using.
8. A Checklist for Writing One
- No space before the
(in a function-like definition. - Parenthesise every parameter where it appears in the replacement.
- Parenthesise the whole replacement, unless it is a single token.
- Use each parameter exactly once, or document that it is evaluated more than once.
- Wrap multiple statements in
do { ... } while (0). - SHOUT the name, so a reader knows the ordinary rules do not apply.
#undefit when it is local to a stretch of code, since a macro has no scope.- Prefer an
enumconstant, a function, orstatic inlinewhenever one of them fits. - Check
-Ewhenever a macro surprises you.
9. What You Should Be Able to Say Now
- Object-like macros replace a name; function-like ones take arguments. The difference is no space before
(. - Substitution is on tokens, not characters — measured:
SIZE_LIMITand"SIZE"were untouched while a standaloneSIZEbecame10. - A macro does not expand inside itself, so recursion cannot occur.
- A macro has no scope, type or linkage — it is gone before any of those exist.
- Parenthesise the parameters:
SQ_BAD(1+2)gave 5 via1+2 * 1+2. - And parenthesise the result:
100/SQ_HALF(2)gave 100 via100/(2) * (2), with every argument already wrapped. - A parameter used twice is evaluated twice:
MAX(i++, j)leftiat 11 from 9, and called a function 2 times. - That is defined behaviour — the conditional operator sequences it — and it only fires when the duplicated operand is selected.
- Prefer a function; use a macro for type-generic code, integer constant expressions, call-site capture, or controlling compilation.
do { ... } while (0)for multi-statement macros — measured: without it,b=1printed although the condition was false, with no warning.#stringizes and##pastes — both impossible in a function.__FILE__and__LINE__are macros;__func__is a predefined identifier and survives preprocessing — visible in the expansion.
10. Exercises
Exercise 1 — fix the macros. Each has at least one defect from §3 or §5. Correct them.
#define AREA(w,h) w * h
#define NEG(x) -(x)
#define SWAP(a,b) t = a; a = b; b = t
#define MID(a,b) ((a) + (b)) / 2
#define ABS(x) ((x) < 0 ? -(x) : (x))Exercise 2 — predict the expansion and the value. Give both for each, using #define SQ(x) x*x and #define N 2+1.
SQ(3)
SQ(N)
10/SQ(2)
N*NExercise 3 — token or character. Say which of these are replaced given #define LEN 8.
int LEN;
int LENGTH;
int my_LEN;
char *s = "LEN";
char c = 'L';
arr[LEN]Exercise 4 — count the evaluations. For each, say how many times next() is called.
#define TWICE(x) ((x) + (x))
#define FIRST(a,b) (a)
#define MAXV(a,b) ((a) > (b) ? (a) : (b))
TWICE(next())
FIRST(next(), next())
MAXV(next(), 0) /* next() returns 5 */
MAXV(next(), 99) /* next() returns 5 */The last two differ. Explain why, and say which chapter's lesson that resembles.
Exercise 5 — the statement trap. Explain why b=1 printed in §5 with flag zero, write the do/while (0) version, and say why plain braces are not sufficient.
Exercise 6 — macro or not. For each, say whether you would use a macro, an enum, or a function, and why.
- the maximum of two
doubles - the size of a fixed buffer, used as an array bound
- a debug print that reports the file and line it was called from
- converting degrees to radians
- the larger of two values of any arithmetic type
Exercise 7 — explain __func__. Using §7's expansion, explain why __func__ is still present after preprocessing, what the compiler does with it, and one thing you therefore cannot do with it that you can do with __FILE__.
Exercise 8 — write a safe macro. Write an ARRAY_LEN(a) macro that gives the element count of an array. State the two things it requires of its argument, which chapter measured what happens when one of them is violated, and what you would write in the header beside it.
11. Summary
- Object-like vs function-like: the difference is no space before
(. - Tokens, not characters —
SIZE_LIMITand"SIZE"untouched; a standaloneSIZEbecame10. - Macros have no scope, type or linkage, and cannot recurse.
- Parenthesise the parameters —
SQ_BAD(1+2)= 5. - Parenthesise the result too —
100/SQ_HALF(2)= 100, with the arguments already wrapped. - A parameter used twice is evaluated twice —
iwent 9 → 11, a function ran 2 times. Defined, and it only fires on the selected branch. - Prefer a function. Macros are for type-generic code, constant expressions, call-site capture, and controlling compilation.
do { ... } while (0)— without it,b=1printed on a false condition, with no warning.#stringizes,##pastes.__FILE__and__LINE__are macros;__func__is a predefined identifier.- Read
-Ewhen a macro surprises you.
Every macro in this chapter produced code. The preprocessor's other ability is to produce none — to look at a condition and delete a whole region of the file before the compiler can object to it, which Chapter 11.2 §3 demonstrated when an #else branch simply vanished from the translation unit. That facility is how one source tree builds for two targets, how a debug build differs from a release one, and how a header adapts to the compiler reading it. It is also how a source file becomes unreadable, how a branch nobody has compiled for two years quietly stops being valid C, and how a bug reproduces on one machine and not another. The next chapter is about using it for genuine build variation without any of those three outcomes.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
