0 · Recall Warm-up
Answer from memory first. Answers are in the Answer Vault at the bottom.
- What is a memory leak? from Lesson 1.7
- What is
sizeof("abc")? from Lesson 1.5.3 - Inside
void f(int *p, int *q) { p = q; *p = 2; }called as f(&i, &j), which variable changes? from Lesson 1.4.2
1 · The Hook
🎣
Before your C file reaches the compiler, a helper called the preprocessor edits it like a find-and-replace tool. Used carelessly, find-and-replace produces surprises: replace “SQ(x)” with “xx” and SQ(2+3) becomes 2+32+3. This lesson covers that tool, and one more ability: storing a function in a variable.
2 · Predict First
🤔
What does this print?
#define SQ(x) x * x
#define SQ2(x) ((x) * (x))
int main(void)
{
printf("%d %d %d\n", SQ(2 + 3), SQ2(2 + 3), 100 / SQ2(5));
return 0;
}
Make a guess in your head. The answer opens Section 3.
3 · Core Concept
💡
Predict First — the answer
Output
11 25 4
SQ(2 + 3) is replaced by 2 + 3 * 2 + 3 = 11, because a macro substitutes text, not values. SQ2 puts brackets around each use and around the whole: ((2 + 3) * (2 + 3)) = 25, and 100 / ((5) * (5)) = 4.
The preprocessor
Lines starting with # are handled by the preprocessor before compilation: #include pastes in a file, #define defines a macro, #ifdef … #endif keeps or drops code.
Object-like macros
#define N 10 replaces every later N with 10. There is no type and no memory, just text. With #define AREA N * N + 1, the expression 2 * AREA becomes 2 * 10 * 10 + 1.
Function-like macros and their traps
#define SQ(x) x * x pastes the argument text where x appears. Three traps:
- Missing brackets around parameters: SQ(2 + 3) → 2 + 3 * 2 + 3 = 11.
- Missing outer brackets: with
#define SQ3(x) (x) * (x), 100 / SQ3(5) → 100 / (5) * (5) = 100. - Side effects repeated: SQ2(i++) becomes ((i++) * (i++)): i changes twice: undefined behavior.
Safe style: bracket every parameter and the whole body, and never pass ++ or function calls with side effects.
#define SQ3(x) (x) * (x)
int main(void)
{
printf("%d\n", 100 / SQ3(5));
return 0;
}
Output
100
Macro vs function
| Macro | Function | |
|---|---|---|
| When | text replaced before compiling | called at run time |
| Types checked | no | yes |
| Arguments | pasted as text (may be evaluated twice) | evaluated once and copied |
| Cost | no call overhead, bigger code | call overhead, one copy of the code |
Function pointers
A function’s name decays to its address, which can be stored in a function pointer:
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int apply(int (*op)(int, int), int x, int y)
{
return op(x, y);
}
int main(void)
{
int (*f)(int, int) = add;
int (*table[2])(int, int) = {add, mul};
printf("%d %d %d %d\n", f(2, 3), (*f)(4, 5), table[1](3, 4), apply(mul, 6, 7));
return 0;
}
Output
5 9 12 42
int (*f)(int, int);— f is a pointer to a function taking two ints and returning int. The brackets matter:int *f(int, int);would declare a function returning int *.- Call it as
f(2, 3)or(*f)(2, 3). - Arrays of function pointers make tables of operations; passing one as an argument (a callback) lets a function such as a sorter use a comparison you supply.
4 · Visual
Expansion is text substitution:
#define SQ(x) x * x
SQ(2 + 3) -> 2 + 3 * 2 + 3 = 2 + 6 + 3 = 11
#define SQ2(x) ((x) * (x))
SQ2(2 + 3) -> ((2 + 3) * (2 + 3)) = 25
#define SQ3(x) (x) * (x)
100 / SQ3(5) -> 100 / (5) * (5) = 20 * 5 = 100 (left to right!)
source.c --[preprocessor: #include, #define]--> expanded text --[compiler]--> executable
Write the expansion out in full, then apply ordinary precedence.
Macros are gone before the compiler sees the program.
5 · Worked Examples
Example 1 Easy
What does this print?
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define N 4
#define AREA N * N + 1
int main(void)
{
int x = 3, y = 7;
printf("%d %d %d\n", MAX(x, y), 2 * AREA, MAX(x + 1, y - 4));
return 0;
}
Solution
MAX(x, y) = 7. 2 * AREA → 2 * 4 * 4 + 1 = 33. MAX(x + 1, y − 4) → ((x + 1) > (y − 4) ? … ) → 4 > 3 → 4.
Output
7 33 4
Example 2 Medium
What does this print?
#define CUBE(x) (x * x * x)
int main(void)
{
int a = 2;
printf("%d\n", CUBE(a + 1));
return 0;
}
Solution
CUBE(a + 1) → (a + 1 * a + 1 * a + 1) = 2 + 2 + 2 + 1 = 7, not 27.
Output
7
Example 3 GATE
What does this print?
#define PROD(a, b) a * b
#define DOUBLE(x) x + x
int main(void)
{
int k = 2;
printf("%d %d\n", PROD(k + 1, k + 2), 3 * DOUBLE(k));
return 0;
}
Solution
PROD(k + 1, k + 2) expands to k + 1 * k + 2. Multiplication first: 1 * k = 2, so 2 + 2 + 2 = 6 (not 3 × 4 = 12). 3 * DOUBLE(k) expands to 3 * k + k = 6 + 2 = 8 (not 12).
Output
6 8
6 · GATE Traps
⚠️
- Macros substitute text: always expand fully before evaluating.
- Bracket every parameter and the whole macro body.
- Arguments with side effects (i++, function calls) may be evaluated more than once.
- An object-like macro such as N * N + 1 is not bracketed either: 2 * AREA ≠ 2 * (N * N + 1).
int (*f)(int)is a function pointer;int *f(int)is a function returning a pointer.- Macros have no type checking and no scope.
8 · Memory Hook
🧠
“Macros are a photocopier, not a calculator.” They copy your text exactly where the parameter was, brackets and all — or without them.
9 · Connect the Dots
Phase 1 is complete: values, operators, control flow, functions, the call stack, pointers, arrays, strings, structures, the heap and the preprocessor. Functions have so far called only other functions. Next phase begins with the question: what happens when a function calls itself?
10 · Self-Test
GATE format. Try every question before opening the vault.
Q1MCQ1 markMacro
What does this print?
#define CUBE(x) (x * x * x)
int main(void)
{
int a = 2;
printf("%d\n", CUBE(a + 1));
return 0;
}
- (A)
8 - (B)
7 - (C)
27 - (D)
9
Q2NAT2 marksMacro
With #define SQ3(x) (x) * (x), what is 100 / SQ3(5)?
Numerical answer: ________
Q3MCQ1 markDeclarations
int (*op)(int, int); declares op as:
- (A) a macro
- (B) a pointer to a function taking two ints and returning int
- (C) a function returning a pointer to int
- (D) an array of two ints
Q4MCQ2 marksFunction pointers
What does this print?
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int apply(int (*op)(int, int), int x, int y)
{
return op(x, y);
}
int main(void)
{
int (*f)(int, int) = add;
int (*table[2])(int, int) = {add, mul};
printf("%d %d %d %d\n", f(2, 3), (*f)(4, 5), table[1](3, 4), apply(mul, 6, 7));
return 0;
}
- (A)
6 20 12 42 - (B)
5 9 12 42 - (C)
5 20 7 13 - (D)
5 9 7 42
Q5MSQ1 markMacros vs functions
Which are true?
- (A) A function argument is evaluated exactly once.
- (B) A macro argument with ++ may be incremented more than once.
- (C) Macro arguments are type-checked.
- (D) A macro is expanded before compilation.
11 · Rule Card
- Preprocessor runs before compiling: #include, #define, #ifdef.
- #define is text substitution: expand fully, then apply precedence.
- Bracket parameters and the whole body: #define SQ(x) ((x) * (x)).
- Side-effect arguments may be evaluated several times (undefined for ++).
- int (*f)(int, int) = add; f(2, 3) or (*f)(2, 3).
- Function pointer arrays and callbacks; int *f(int) is different.
12 · Concept Ledger
Unlocked in this lesson. From here on, these terms may be used.
preprocessor#defineobject-like macrofunction-like macromacro expansionfunction pointercallbackarray of function pointers
13 · Answer Vault
Answer Vault
Try the questions first. Open when you are ready to check.
Recall Warm-up
- Losing the last pointer to a heap block without freeing it.
- 4 (three characters and '\0').
- j.
Self-Test
Q1 Answer: (B)1 mark
a + 1 * a + 1 * a + 1 = 2 + 2 + 2 + 1 = 7.
Output
7
- (A) is wrong: Expand the text fully.
- (C) is wrong: Would need brackets around x.
- (D) is wrong: Not (a + 1) squared.
Re-read: Lesson 1.8 — Preprocessor macros and their expansion traps; function pointers
Q2 Answer: 1002 marks
100 / (5) * (5) = 20 × 5 = 100.
Re-read: Lesson 1.8 — Preprocessor macros and their expansion traps; function pointers
Q3 Answer: (B)1 mark
The brackets bind * to op first.
- (A) is wrong: Macros use #define.
- (C) is wrong: That is int *op(int, int).
- (D) is wrong: No array brackets.
Re-read: Lesson 1.8 — Preprocessor macros and their expansion traps; function pointers
Q4 Answer: (B)2 marks
add(2, 3) = 5; add(4, 5) = 9; table[1] = mul: 12; apply(mul, 6, 7) = 42.
Output
5 9 12 42
- (A) is wrong: add(2, 3) = 5.
- (C) is wrong: f is add, not mul.
- (D) is wrong: table[1] is mul: 3 × 4 = 12.
Re-read: Lesson 1.8 — Preprocessor macros and their expansion traps; function pointers
Q5 Answer: (A) (B) (D)1 mark
Macros have no type checking.
- (C) is wrong: No types exist at expansion time.
Re-read: Lesson 1.8 — Preprocessor macros and their expansion traps; function pointers