The C Preprocessor is a simple text substitution tool that runs before the actual C compiler. The #define directive is used to create macros. Macros are not functions; they are instructions to the preprocessor to replace a symbol with a defined piece of text.
1. Object-like Macros (Symbolic Constants)
An object-like macro defines a simple symbolic constant, similar to using the const keyword, but handled at the preprocessor stage.
Syntax: #define MACRO_NAME replacement_text
- Benefit: Zero runtime overhead, as the name is replaced by the value before compilation.
- Convention: Macros are typically written in
ALL_CAPS.
#define ARRAY_SIZE 50
#define PI 3.1415926535
#define DEBUG_MODE 1
// The compiler sees: int arr[50];
int arr[ARRAY_SIZE];
2. Function-like Macros
Function-like macros accept arguments and are often used as a performance-optimized alternative to small, inline functions. The arguments are substituted directly into the replacement text.
Syntax: #define MACRO_NAME(arg1, arg2) replacement_code
- Key Feature: There should be no space between the macro name and the opening parenthesis.
Example: Calculating the Square
// Macro to calculate the square of a number
#define SQUARE(x) ((x) * (x))
int a = 5;
// The call SQUARE(a) is replaced with ((a) * (a))
int result = SQUARE(a);
3. Dangers of Macro Expansion (The Precedence Trap)
Because macros involve simple text substitution, they can easily lead to incorrect results due to operator precedence issues that are not present in normal functions.
Example of Precedence Failure
Consider a macro for addition:
#define ADD(a, b) a + b // Incorrect definition!
int y = ADD(2, 3) * 5; // We intend (2 + 3) * 5 = 25
// Preprocessor substitution: 2 + 3 * 5
// C precedence rules: 3 * 5 = 15, then 2 + 15 = 17 (WRONG)
The Solution: Full Parenthesization
The safest practice for function-like macros is to enclose each argument and the entire macro definition in parentheses.
// Correct definition: ensures proper grouping
#define ADD_SAFE(a, b) ((a) + (b))
int z = ADD_SAFE(2, 3) * 5;
// Preprocessor substitution: ((2) + (3)) * 5
// C precedence rules: (5) * 5 = 25 (CORRECT)
4. Other Macro Operators
The preprocessor provides two special operators for working within macros:
| Operator | Name | Purpose | Example |
# | Stringizing | Converts its argument into a double-quoted string literal. | #define STR(x) #x $\rightarrow$ STR(value) becomes "value" |
## | Token Pasting | Joins two tokens (words/symbols) into a single new token. | #define JOIN(a, b) a##b $\rightarrow$ JOIN(x, 1) becomes x1 |
Example: Stringizing
#define VAR_TO_STR(var) #var
int counter = 10;
// printf receives the string "counter"
printf("Variable name is: %s\n", VAR_TO_STR(counter));
