Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #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.

Azvya Education Pvt. Ltd.VLSI Mentor
what does and does not get replaced
#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;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
SIZE=10  SIZE_LIMIT=1  sizeof arr=40  s="SIZE"

And the expansion:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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_LIMIT is a single identifier token that happens to begin with the letters SIZE, so it is not touched. The contents of a string literal are one token, so "SIZE" is not touched. Only the standalone SIZE token became 10.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
three versions of the same macro
#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 */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #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.

Azvya Education Pvt. Ltd.VLSI Mentor
making it fire
#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;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
MAX(i++, j) with i=9, j=5:  m=10  i=11
MAX(reading(), 15): n=50  reading() was called 2 time(s)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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.

Azvya Education Pvt. Ltd.VLSI Mentor
with flag == 0, so neither should print
#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");
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 an if branch can occupy, and it still requires a trailing semicolon at the call site — so LOG_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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #define SHOW(expr)   printf(#expr " = %d\n", (expr))

    SHOW(a + b);
    SHOW(a * b + 1);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a + b = 7
a * b + 1 = 13
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #define FIELD(n)     field_##n
    printf("FIELD(1)=%d FIELD(2)=%d\n", FIELD(1), FIELD(2));
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FIELD(1)=11 FIELD(2)=22
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    printf("file %s line %d func %s\n", __FILE__, __LINE__, __func__);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
file m7.c line 11 func main
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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

  1. No space before the ( in a function-like definition.
  2. Parenthesise every parameter where it appears in the replacement.
  3. Parenthesise the whole replacement, unless it is a single token.
  4. Use each parameter exactly once, or document that it is evaluated more than once.
  5. Wrap multiple statements in do { ... } while (0).
  6. SHOUT the name, so a reader knows the ordinary rules do not apply.
  7. #undef it when it is local to a stretch of code, since a macro has no scope.
  8. Prefer an enum constant, a function, or static inline whenever one of them fits.
  9. Check -E whenever a macro surprises you.

9. What You Should Be Able to Say Now

  1. Object-like macros replace a name; function-like ones take arguments. The difference is no space before (.
  2. Substitution is on tokens, not characters — measured: SIZE_LIMIT and "SIZE" were untouched while a standalone SIZE became 10.
  3. A macro does not expand inside itself, so recursion cannot occur.
  4. A macro has no scope, type or linkage — it is gone before any of those exist.
  5. Parenthesise the parameters: SQ_BAD(1+2) gave 5 via 1+2 * 1+2.
  6. And parenthesise the result: 100/SQ_HALF(2) gave 100 via 100/(2) * (2), with every argument already wrapped.
  7. A parameter used twice is evaluated twice: MAX(i++, j) left i at 11 from 9, and called a function 2 times.
  8. That is defined behaviour — the conditional operator sequences it — and it only fires when the duplicated operand is selected.
  9. Prefer a function; use a macro for type-generic code, integer constant expressions, call-site capture, or controlling compilation.
  10. do { ... } while (0) for multi-statement macros — measured: without it, b=1 printed although the condition was false, with no warning.
  11. # stringizes and ## pastes — both impossible in a function.
  12. __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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
SQ(3)
SQ(N)
10/SQ(2)
N*N

Exercise 3 — token or character. Say which of these are replaced given #define LEN 8.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#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_LIMIT and "SIZE" untouched; a standalone SIZE became 10.
  • 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 — i went 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=1 printed on a false condition, with no warning.
  • # stringizes, ## pastes.
  • __FILE__ and __LINE__ are macros; __func__ is a predefined identifier.
  • Read -E when 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.