Intuition The one core idea
A macro and an inline function both try to make a tiny operation fast by pasting its work directly where you use it, instead of "phoning" a faraway function. The whole topic is a battle between two ways of pasting: blind text copying (the preprocessor) versus smart, type-aware copying (the compiler) — and every trap in the parent note is a consequence of which one you chose.
Before you can judge that battle, you must own every symbol the parent note throws at you. Below, each piece is built from nothing: plain words → a picture → why the topic needs it. Read top to bottom; each rung stands on the one below.
Definition Source code = plain text
Your .c file is nothing but characters in a text file — letters, digits, spaces, symbols like +, *, (, ). The computer does not understand it yet. Something must translate it.
Intuition Why start here?
Every trap in this topic comes from who reads the text and how much they understand . So the first symbol to define is "text" itself. A macro treats your code as just text . The compiler treats it as meaningful C . That single difference is the whole chapter.
Look at the figure: the same line SQUARE(2 + 3) is shown twice. On the left a hand simply copies the letters (that is the preprocessor's view). On the right the letters are first understood as numbers and operations (that is the compiler's view). Keep this picture in your head for the entire topic — it explains everything that follows.
Definition The preprocessor
A first-pass tool that scans your file for lines starting with # and does text edits — copy, paste, replace, include — before any real translation happens. It knows nothing about C meaning. See C Preprocessor .
The tool that runs after the preprocessor, reads the now-edited text, and turns it into machine instructions. It understands types, scope, and evaluation rules — it is the "smart helper."
Intuition Why the topic needs both
The parent note's entire pipeline diagram is: preprocessor first → compiler second . A macro is resolved in step one (blind); an inline function is resolved in step two (smart). Every advantage of inline functions ("type checking", "evaluated once", "debuggable") exists only because it lives in the second, smarter stage .
#define SQUARE ( x ) ((x) * (x)) // <- the # means: preprocessor, handle me
The # is not decoration. It is a flag that says: "this line is for the first translator, not the second." Everything after #define is a rule the preprocessor obeys literally .
#define NAME replacement
Reads as: "Everywhere you later see NAME, delete it and paste replacement in its place — as raw text. " Nothing more, nothing less.
Definition Function-like macro
#define NAME(x) body
Same idea, but with a placeholder x. When you write NAME(something), the preprocessor pastes body and inside it replaces every x with the literal text something.
Follow the arrows in the figure. x is a hole . Whatever text you drop into the hole gets stamped into every place x appears in the body — as characters, uncalculated. This is why the parent's SQUARE(2 + 3) becomes the literal string ((2 + 3) * (2 + 3)): the four characters 2 + 3 are pasted twice, verbatim.
Common mistake "The macro adds up
2+3 first, then squares."
Why it feels right: that is what a function would do. The truth: the preprocessor cannot add — it only copies letters . It has no idea 2 + 3 means five. The adding happens later , in the compiler, on whatever text was pasted.
Definition Operator precedence
A fixed rule about which operation happens first when symbols meet. * binds tighter than +, so 2 + 3 * 2 means 2 + (3 * 2) = 8, not (2 + 3) * 2 = 10. See Operator Precedence in C .
( )
A way to force grouping , overriding precedence. (2 + 3) * 2 means "add first, then multiply." Parentheses are the compiler's instruction "do this box before touching anything outside it."
Intuition Why macros must be drowned in parentheses
Because the preprocessor pastes text into the middle of a bigger expression, the surrounding operators can "reach in" and grab pieces. Wrapping the placeholder — ((x) * (x)) — builds walls the outside operators can't cross.
The figure shows both trees for #define BAD(x) x*x used as BAD(2+3). Without parens the pasted text is 2+3*2+3; precedence makes * grab only the adjacent 3 and 2, giving the red wrong subtree = 11. With parens, the walls force (2+3) to resolve as one unit before squaring, giving 25. The trap is not in the macro — it is in precedence acting on carelessly pasted text .
x++ (post-increment)
An expression that ==uses the current value of x, then adds 1 to x==. Writing x++ twice means x grows by 2. The "add 1" part is called a side effect : a lasting change beyond just producing a value.
Intuition Why this matters here
A macro pastes its placeholder text as many times as it appears in the body . If you feed it i++, and the body uses x twice, then i++ is stamped twice — so i increments twice . That is the parent's double-evaluation trap. See Undefined Behavior in C .
#define SQ ( x ) ((x) * (x))
int i = 3 ;
int r = SQ (i ++ ); // becomes ((i++)*(i++)) -> i changed twice
An inline function sidesteps this because the argument i++ is computed once into a parameter before the body ever runs — a concept we build in section 7.
Definition Function call overhead
The hidden cost of jumping to a function: push arguments, jump away, save registers, run, return, clean up. For a huge function this cost is invisible; for x*x it can dwarf the actual work. See Function Call Overhead .
The figure contrasts two paths. The left path calls a function: control leaves the current spot, travels to the function, and returns — the round-trip is the overhead. The right path has the function's body pasted in place (inlined), so there is no trip at all. Both macros and inline functions aim for the right-hand picture; they just choose different translators to do the pasting.
inline
A keyword you attach to a function to suggest the compiler paste its body at the call site. Crucially it is a request, not a command . See Compiler Optimization .
static inline int square ( int x ) { return x * x; }
inline forces inlining."
Why it feels right: the word literally says "inline." The truth: the compiler may refuse (e.g., large or recursive functions). It is a hint about intent , plus a rule about linkage — never a guarantee.
The compiler verifies that values fit their declared kinds — you can't square a word, can't pass a string where an int belongs. Macros skip this entirely; inline functions enforce it.
The region where a name is valid. A function's parameter x is a local variable that exists only inside it — it can't accidentally collide with your outer x. A macro's x is just pasted text and respects no such boundary.
Intuition Why these are the payoff
"Evaluated exactly once", "type-safe", "debuggable", "has an address" — every inline advantage in the parent's comparison table is a free gift of being a real, compiler-understood function with genuine parameters and scope. The typed alternative for constants lives in const and constexpr .
Source code is plain text
Parentheses force grouping
Macros vs inline functions
Test yourself — cover the right side.
Is a .c file text or already machine code before translation? Just plain text — meaningless to the CPU until translated.
Which translator runs first, preprocessor or compiler? The preprocessor , doing pure text edits before the compiler.
What does #define SQ(x) ((x)*(x)) literally do to SQ(5)? Pastes the body with x replaced by 5, giving ((5)*(5)) as text.
Does the preprocessor calculate 2+3? No — it only copies characters ; the compiler does the arithmetic later.
Why does * grab pieces of 2+3*2+3? Because * has higher precedence than +, so it binds before addition.
What do parentheses do to precedence? They force grouping , making the inside resolve first regardless of operators.
How many times does i++ run in ((i++)*(i++))? Twice — i is incremented twice, the double-evaluation trap.
What is function call overhead? The cost of jumping, saving registers, and returning — the round-trip both techniques remove.
Is inline a guarantee? No — a request/hint the compiler may ignore.
Name one gift of being a real function (not a macro). Type checking, single argument evaluation, scope, debuggability, or having an address.
Parent topic ↑
C Preprocessor — the first translator that does the text pasting
Operator Precedence in C — why parentheses in macros are non-negotiable
Function Call Overhead — the cost these techniques exist to remove
Undefined Behavior in C — where double evaluation leads
const and constexpr — typed alternatives to constant macros
Compiler Optimization — when inline is honored