5.1.28 · D5C Programming
Question bank — Macros vs inline functions
True or false — justify
Every macro invocation is slower to compile-check than an inline call.
False. Macros aren't type-checked at all by the C Preprocessor — they're pure text, so there's no checking to be slow; the danger is that errors slip through, not that checking is heavy.
inline forces the compiler to paste the function body at the call site.
False.
inline is a request/hint; the optimizer may ignore it (e.g. recursive or huge functions) and still emit a real call.A macro and an inline function that both "square x" always produce identical machine code.
False. Only when the argument has no side effects and no precedence surprise.
SQ(i++) differs: the macro evaluates i++ twice, the inline function once.An inline function can have its address taken with &.
True. It is a real function with a single definition, so
&square is a valid pointer; a macro is just text and has no address.Wrapping every macro parameter in parentheses removes all macro dangers.
False. Parentheses fix the precedence trap but do nothing for double-evaluation of side-effect arguments like
i++.A macro respects the scope of local variables the same way a function does.
False. The preprocessor pastes global text; a macro can accidentally capture or collide with names in the surrounding scope because it has no notion of scope.
Because it avoids a jump, a correctly-written macro is always faster than an inline function.
False. An inlined function also has no call jump, and it evaluates each argument once — so it often produces equal or better code.
You can set a breakpoint inside a macro to step through it in a debugger.
False. After preprocessing the macro's "code" merges into the call-site line, so debuggers can't stop inside it; inline functions remain debuggable.
The preprocessor understands C operator precedence when expanding #define.
False. It only knows tokens and text;
#define BAD(x) x*x expands BAD(2+3) to 2+3*2+3, which precedence then evaluates as 11.An inline function in C can be generic over int, double, and char at once.
False. C has no overloading, so one inline function locks one type signature; type-generic behaviour is a genuine reason to reach for a macro.
Spot the error
#define AREA(w,h) w*h — later used as 1/AREA(2,3).
Error: expands to
1/2*3 = 1 (should be 1/6). No parentheses around the whole body or params; write ((w)*(h)).#define SQ(x) ((x)*(x)) then SQ(i++).
Error:
i++ is substituted twice → ((i++)*(i++)), so i is incremented twice — undefined behaviour. An inline function would evaluate i++ once.#define MAX(a,b) ((a)>(b)?(a):(b)) then MAX(p++, q++).
Error: the winning argument sits inside the
?: twice, so whichever branch is chosen is incremented twice. Even fully parenthesized, side-effect args stay unsafe in macros.#define SWAP(a,b) { int t=a; a=b; b=t; } used as if (x) SWAP(p,q); else foo();.
Error: the trailing
; after SWAP(p,q) closes the if, orphaning the else. Use the do { ... } while(0) idiom so the whole thing is one statement.#define PI 3.14; then area = PI * r * r;.
Error: the stray
; becomes area = 3.14; * r * r; — a syntax error. Constant macros must not end in a semicolon; prefer const anyway.static inline int cube(int x) { return x*x*x; } called as cube(1000000).
Error: not the inlining — the
int arithmetic overflows silently, still UB. Inline fixes call problems, not type-range problems; choose a wider type.#define DBL(x) 2*x used as DBL(a+b).
Error: expands to
2*a+b, not 2*(a+b). Missing parentheses around the argument; write (2*(x)).Why questions
Why can't a macro's argument evaluation be "fixed" by cleverer parentheses?
Because parentheses only change grouping of the pasted text; the argument text still literally appears as many times as it's used, so its side effects still fire that many times.
Why does an inline function evaluate each argument exactly once even when inlined?
The language semantics compute each argument into a parameter before the body runs; inlining is a copy of already-evaluated behaviour, not a re-textual paste.
Why is inline described as affecting linkage, not just speed?
In C,
inline changes how the definition is emitted across translation units (one-definition rules); the actual pasting is left to the optimizer.Why do we still teach macros if inline functions are safer?
Macros do things functions can't: type-generic code, stringizing
#x, token-pasting a##b, and conditional compilation — non-function powers of the C Preprocessor.Why does the macro trap happen "before the compiler even starts"?
Text substitution runs in the preprocessing phase, so type checking never sees the original call — it only sees already-mangled text, and can't warn you.
Why does removing call overhead matter most for tiny operations?
If the work (e.g. one multiply) is smaller than the setup cost (push, jump, save, return), the overhead dominates — so eliminating it gives the biggest relative win.
Why is do { ... } while(0) preferred over just { ... } for multi-statement macros?
A bare block still leaves a stray
; after the macro call that can break if/else; do{...}while(0) swallows exactly one ; and behaves as a single statement.Edge cases
What happens with a macro that takes zero arguments, like #define NOW time(0)?
It's fine as pure substitution, but every use re-runs
time(0); if you expected one cached value you'll be surprised — a const/inline gives you one evaluation.What if you pass a function call with side effects into a macro, e.g. SQ(read())?
read() is invoked twice, potentially consuming two inputs. Inline square(read()) reads once — this is the double-evaluation trap with I/O.What if the compiler refuses to inline your inline function?
It falls back to a normal call with overhead but still correct semantics; correctness never depends on inlining, only performance.
What happens if a macro name collides with a later variable name?
The preprocessor blindly replaces the identifier everywhere, silently corrupting unrelated code — a scope hazard functions can't cause. Convention: UPPER_CASE macro names to reduce clashes.
What is the result of #define EMPTY then EMPTY x?
EMPTY expands to nothing, leaving just x — an object-like macro can vanish entirely, which is intentional for conditional-compilation guards.What about a macro passed as an argument to itself, e.g. recursive #define?
The preprocessor does not recurse into the same macro during its own expansion, so it silently stops — unlike a truly recursive inline function which the compiler simply won't inline.
Connections
- C Preprocessor — where blind text substitution lives
- Operator Precedence in C — the parenthesis traps above
- Function Call Overhead — the cost both techniques remove
- Undefined Behavior in C — double-evaluation and overflow
- const and constexpr — typed alternative to constant macros
- Compiler Optimization — when
inlineis honored