6. Preprocessor, Macros & Type Qualifiers
Macro expansion traps, stringification (#), token pasting (##), include guards, do-while(0) idiom, const correctness variations, volatile, and restrict in C
GATE CS & UGC NET JRF Core Subject Exam Weightage: 2 Questions every year on macro argument precedence errors, multi-statement macro scoping, and the exact difference between
const int *,int * const, andvolatile.
1. Technical Jargon & The Compilation Pipeline#
Before the C compiler compiles code into assembly, the source file passes through the C Preprocessor (CPP), an automated textual substitution tool.
Source File (.c) ──> [ Preprocessor ] ──> Preprocessed Source (.i)
│
- Expands #include headers
- Replaces #define macros
- Strips comments
- Evaluates #ifdef / #if conditionals
- Tokenization: Breaking the character stream into discrete syntactic lexical tokens.
- Macro Expansion: Textually substituting a macro name with its replacement list.
- Stringification Operator (
#): Converts a macro parameter into a string literal enclosed in double quotes. - Token-Pasting / Concatenation Operator (
##): Merges two separate lexical tokens into a single valid token during preprocessing. - Include Guard: Preprocessor idiom preventing a header file from being included multiple times within the same translation unit.
- Type Qualifier: Keywords (
const,volatile,restrict) that modify how the compiler accesses and optimizes memory. - Strict Aliasing: The ISO C optimization rule stating that two pointers of different incompatible types cannot point to the same memory object (with few exceptions like
char*).
2. Macro Pitfalls & Syntax Traps#
Macros are simple textual substitutions. They have no type checking, no scope, and no knowledge of C operator precedence!
2.1 The Missing Parentheses Trap#
// NAIVE MACRO (Dangerous!):
#define SQUARE(x) x * x
int main(void) {
int res = SQUARE(2 + 3);
// Expands textually to: 2 + 3 * 2 + 3
// By precedence: 2 + (3 * 2) + 3 = 2 + 6 + 3 = 11!
// Expected: (2 + 3)^2 = 25!
printf("%d\n", res); // Prints 11
return 0;
}
// CORRECT DEFENSIVE MACRO:
#define SQUARE(x) ((x) * (x))
// SQUARE(2 + 3) expands to: ((2 + 3) * (2 + 3)) = 25
2.2 The Side-Effect Argument Trap#
Even fully parenthesized macros fail when passed arguments containing side-effects (++, --):
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = 5, y = 10;
int z = MAX(x++, y++);
// Expands to: ((x++) > (y++) ? (x++) : (y++))
// Since 5 > 10 is FALSE:
// y++ is evaluated TWICE!
// y becomes 12, z becomes 11, x becomes 6!
[!CRITICAL] GATE Rule: Never pass expressions with side effects (
i++, function calls) to macros.
2.3 The Multi-Statement Macro Trap & do { ... } while(0)#
If a macro contains multiple statements, wrapping it in curly braces { ... } breaks inside if-else statements:
// BROKEN MACRO:
#define SWAP(a, b) { int temp = a; a = b; b = temp; }
if (condition)
SWAP(x, y); // The trailing semicolon causes: { ... }; else
else // SYNTAX ERROR: 'else' without a previous 'if'!
foo();
// THE CANONICAL IDIOM: do { ... } while(0)
#define SWAP(a, b) do { int temp = a; a = b; b = temp; } while(0)
if (condition)
SWAP(x, y); // Expands to: do { ... } while(0); else
else // Syntactically PERFECT!
foo();
3. Stringification (#) and Token-Pasting (##)#
#include <stdio.h>
// 1. Stringification (#): Converts parameter into "string literal"
#define PRINT_INT(x) printf(#x " = %d\n", x)
// 2. Token-Pasting (##): Merges two tokens into one variable name
#define MAKE_VAR(prefix, id) prefix##_##id
int main(void) {
int score = 95;
PRINT_INT(score); // Expands to: printf("score" " = %d
", score); Prints "score = 95"
int MAKE_VAR(student, 101) = 42; // Expands to: int student_101 = 42;
printf("%d\n", student_101);
return 0;
}
4. Type Qualifiers: const, volatile, restrict#
4.1 Decoding the 3 const Pointer Variations#
Read the declaration backwards (from right to left) starting from the identifier:
1. const int *p;
Read: 'p is a pointer to an integer that is CONSTANT.'
- The data (*p) is read-only: *p = 20; (ERROR)
- The pointer (p) can change: p = &other; (LEGAL)
2. int * const p = &x;
Read: 'p is a CONSTANT pointer to an integer.'
- The pointer (p) is locked: p = &other; (ERROR)
- The data (*p) is mutable: *p = 20; (LEGAL)
3. const int * const p = &x;
Read: 'p is a CONSTANT pointer to a CONSTANT integer.'
- Neither the address nor the data can be modified.
4.2 The volatile Qualifier#
The volatile keyword tells the compiler: "The value of this variable can change at any time through means outside the compiler's control."
// Hardware Memory-Mapped Register Example:
volatile unsigned int *status_reg = (unsigned int *)0x40001000;
// Without 'volatile', the compiler sees no writes to *status_reg in the loop
// and optimizes this into an INFINITE LOOP reading the register ONCE!
while (*status_reg == 0) {
// Wait for hardware to set bit
}
// With 'volatile', the compiler forces a fresh RAM/Bus read on EVERY iteration!
When to Use volatile:#
- Memory-mapped I/O peripheral registers in embedded systems and device drivers.
- Global variables modified by Interrupt Service Routines (ISRs).
- Flags shared across threads in multithreading (though atomic types are preferred in C11).
4.3 The restrict Qualifier (C99)#
restrict is an optimization contract between the programmer and the compiler applying exclusively to pointers:
void vector_add(int * restrict a, int * restrict b, int * restrict c, int n) {
for (int i = 0; i < n; i++) {
a[i] = b[i] + c[i];
}
}
By declaring a, b, and c as restrict, you guarantee to the compiler that arrays a, b, and c do not overlap in memory (no aliasing). This allows the compiler to aggressively vectorize loops and cache values in CPU registers without fear that writing to a[i] will overwrite b[i].
5. Best Practices & Defensive Habits#
- Prefer
static inlineFunctions Over Complex Macros: Inline functions provide the exact same zero-overhead performance as macros while offering strict type checking, clean debugging, and zero side-effect double-evaluation hazards. - Always Wrap Macro Definitions in Parentheses:
Parenthesize both the whole expression
((...))and each parameter usage((x)). - Use Standard Include Guards:
C
#ifndef MY_MODULE_H #define MY_MODULE_H // Declarations... #endif /* MY_MODULE_H */
Practice Quiz
Test your understanding with step-by-step solutions
Practice Quiz
10 questions · 90s per question
Each question has a 90-second time limit. Unanswered questions will be auto-submitted when time runs out.