A macro call can look like a function call, but it behaves differently: the preprocessor substitutes tokens before C expression rules are applied. That is why a harmless-looking SQUARE(3 + 2) can produce 11 instead of 25, and why an increment passed to a macro can run twice. Macro analysis starts with token expansion, followed by grouping and sequencing, and ends with calculation. Object-like and function-like macros, #, ##, conditional compilation, header visibility and exam-style traps all arise from preprocessing behavior.
Macros in C: what the preprocessor changes before compilation
A macro is a named preprocessing replacement created with #define. An object-like macro has no parameter list:
#define CAPACITY 8
int slots[CAPACITY];The first substitution makes the declaration int slots[8];. A function-like macro accepts argument tokens:
#define DOUBLE(x) (2 * (x))Neither kind creates a runtime object, stack frame or parameter variable. Macro invocations expand before the resulting C tokens are compiled. The compiler type-checks only the C tokens left after expansion. A macro parameter has no type of its own and is not type-checked at the invocation.
For ordinary parameter uses, expand nested macros in each argument, substitute those tokens, rescan the replacement, then apply C grouping, sequencing and evaluation. The # and ## operators follow special rules.
Object-like, function-like and nested macros
Consider three definitions:
#define BASE 4
#define STEP (BASE + 1)
#define TWICE(x) (2 * (x))
int value = TWICE(STEP);Because x is not used with # or ##, STEP expands before substitution:
Preprocessing:
STEP -> (BASE + 1) -> (4 + 1)
TWICE(STEP) -> (2 * ((4 + 1)))
C evaluation:
(2 * ((4 + 1))) -> (2 * 5) -> 10This separates preprocessing from C evaluation. STEP is replaced until the argument is fully expanded; only the compiler evaluates 4 + 1.
Compare this with static inline int twice(int x) { return 2 * x; }. The function has an int parameter and evaluates its argument once for that parameter. A macro may use an argument zero, one or several times. The compiler decides whether either form becomes a call, so speed is not an inherent macro advantage.
Macro parentheses trap: why SQUARE(3 + 2) becomes 11
Start with an unsafe definition:
#define SQUARE(x) x * xSQUARE(3 + 2) expands to 3 + 2 * 3 + 2. Multiplication comes first: 2 * 3 = 6, followed by 3 + 6 + 2 = 11. The macro did not square 5; it inserted argument tokens without grouping.
Correct it as #define SQUARE(x) ((x) * (x)). The call now expands to ((3 + 2) * (3 + 2)). Each grouped sum is 5, so the result is 5 * 5 = 25. Parenthesise every parameter use and the entire replacement expression.
Also, with the unsafe definition, 100 / SQUARE(5) becomes 100 / 5 * 5. Division and multiplication associate left to right, so (100 / 5) * 5 = 20 * 5 = 100. With the corrected macro, 100 / ((5) * (5)) = 100 / 25 = 4.

Macro side effects: a complete MAX(i++, j++) trace
Parentheses do not prevent repeated evaluation:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int i = 4, j = 7;
int m = MAX(i++, j++);Expand completely before calculating:
int m = ((i++) > (j++) ? (i++) : (j++));The comparison reads the old values, so 4 > 7 is false. Those postfix operations leave i = 5 and j = 8. Only the false branch is selected. Its j++ yields the old value 8 for m, then changes j to 9. The final state is m = 8, i = 5, j = 9.
Both argument spellings occur twice, and the selected one is evaluated again. Better parentheses cannot make this macro single-evaluation. Pass side-effect-free expressions, or first store i++ and j++ in separate temporary values and compare those.

Stringification, token pasting and conditional compilation
The # and ## operators have preprocessing roles:
#define TEXT(x) #x
#define JOIN(a, b) a##b
int mark_2 = 84;
printf("%s %d", TEXT(GATE CS), JOIN(mark_, 2));TEXT(GATE CS) becomes the string literal "GATE CS". JOIN(mark_, 2) pastes the tokens into the identifier mark_2. Output: GATE CS 84. Operands next to # or ## are not macro-expanded first. These are not ordinary runtime C operators.
Conditional compilation selects source during preprocessing:
#define DEBUG 1
#if DEBUG
puts("trace");
#endifHere DEBUG becomes 1, so the guarded line remains and prints trace when executed. If DEBUG is defined as 0, the line is excluded from that translation. This is build-time selection, not a runtime if.
Macro visibility, headers and statement-macro traps
A macro name means whatever its current definition says at the line where it is used. With #define SIZE 8, int first[SIZE]; becomes int first[8];. After #undef SIZE and #define SIZE 16, int second[SIZE]; becomes int second[16];. Braces do not create macro scope. A definition lasts until #undef or the end of that preprocessing translation unit.
A header guard uses that visibility within one translation unit:
#ifndef MATRIX_H
#define MATRIX_H
#define ROWS 2
void clear_matrix(int matrix[ROWS][ROWS]);
#endifOn a second inclusion, MATRIX_H is defined, so the body is skipped. Separately compiled source files have separate preprocessing translation units.
The same few mistakes repeatedly break macro expansion, and each has a direct fix worth recognizing.
Mistake | What expansion does | Fix |
|---|---|---|
Missing parameter or whole-expression parentheses | Changes grouping around substituted tokens | Write |
Trailing semicolon in | Can insert a statement-ending | Leave the semicolon to the caller |
Several bare statements after an | Only the first statement is controlled reliably | Use a |
Side-effecting argument such as | Repeats the side effect when the parameter is repeated | Pass a temporary or use a function |
For a statement-like macro, the standard wrapper keeps it single:
#define SWAP_INT(a, b) do { int temp = (a); (a) = (b); (b) = temp; } while (0)
int x = 2, y = 9;
SWAP_INT(x, y);The result is x = 9, y = 2. The wrapper fixes statement structure, but arguments must still be suitable assignable expressions without troublesome side effects.
Macro exam questions: expand first, then classify the trap
Under exam pressure, it is easy to compute a macro mentally and miss the trap, or to get the value without knowing what the question is testing. Never evaluate a macro in your head. Expand it fully in writing first, then read the result for missing parentheses, repeated argument evaluation, stringify or paste behavior, or conditional compilation.
Use a five-pass answer method:
Write the macro definition exactly as given.
Expand nested macro names in ordinary arguments before substitution.
Substitute, rescan the replacement, and handle # or ## by their special rules.
Mark grouping and sequencing in the resulting C expression.
Compute the value, or reject an unsafe or non-portable shortcut.
Apply the five passes to the unsafe SQUARE(3 + 2). Substitution produces 3 + 2 * 3 + 2, which evaluates to 11. Pass 4 exposes the missing parentheses, while the safe form produces ((3 + 2) * (3 + 2)) and gives 25. Next, run the same five passes on MAX(i++, j++) with i = 4 and j = 7. The trace finishes at m = 8, i = 5 and j = 9, making the repeated-evaluation trap visible. For the wider route, continue with GATE CS Exam Preparation.
Macros in C: the short version and next step
Remember five rules: macros replace tokens before compilation; nested names are rescanned; each parameter and the whole expression need parentheses; repeated arguments must not receive side effects; and #, ##, conditional compilation and header guards are preprocessing tools. The numerical anchors are TWICE(STEP) = 10, corrected SQUARE(3 + 2) = 25 instead of 11, and m = 8, i = 5, j = 9 after the side-effect trace. Continue with the C Language Course for structured C concepts and practice, or GATE Guidance by Sanchit Sir for the wider GATE CS sequence.




