Macros & Preprocessor
What is a macro? (quick recap)
A macro is a rule you create with #define that the C preprocessor uses to replace text in your code before the compiler runs. If preprocessor basics like #include and include guards are new to you, read preprocessor directives first. This lesson focuses specifically on macros — the most powerful and most dangerous part of the preprocessor.
Object-like macros (constants)
The simplest macros just give a name to a fixed value. These are called object-like macros.
#include <stdio.h>
#define PI 3.14159
#define GREETING "Hello!"
int main() {
printf("PI = %f\n", PI);
printf("%s\n", GREETING);
return 0;
}PI = 3.141590
Hello!
Before compiling, the preprocessor literally replaces every PI with 3.14159 and every GREETING with "Hello!".
Function-like macros
Macros can also take arguments, looking a lot like functions — but they are still just text replacement.
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
printf("Square = %d\n", SQUARE(5));
printf("Max = %d\n", MAX(10, 20));
return 0;
}Square = 25
Max = 20
Why parentheses are critical
This is the number-one macro trap. Because macros are blind text substitution, missing parentheses can silently give wrong answers.
#include <stdio.h>
#define BAD(x) x * x // no parentheses
#define GOOD(x) ((x) * (x)) // safe
int main() {
printf("BAD(2+3) = %d\n", BAD(2+3)); // 2+3*2+3 = 11
printf("GOOD(2+3) = %d\n", GOOD(2+3)); // (2+3)*(2+3) = 25
return 0;
}BAD(2+3) = 11
GOOD(2+3) = 25
In a function-like macro, wrap every argument and the whole body in parentheses. This single habit prevents most macro bugs.
The # and ## operators
Two special operators exist only inside macros. # turns an argument into a string; ## pastes two tokens together.
#include <stdio.h>
#define SHOW(x) printf(#x " = %d\n", x) // stringize
#define JOIN(a,b) a ## b // token paste
int main() {
int value = 42;
SHOW(value); // prints: value = 42
int JOIN(my, Num) = 7; // becomes: int myNum = 7;
printf("myNum = %d\n", myNum);
return 0;
}value = 42
myNum = 7
Notice how #x printed the text "value", and JOIN(my, Num) built the identifier myNum.
Macros vs functions
| Point | Macro | Function |
|---|---|---|
| When it runs | Before compilation (text) | At run time |
| Type checking | None | Yes |
| Speed | No call overhead | Small call overhead |
| Debugging | Hard | Easy |
| Side-effect safety | Risky | Safe |
Dangerous pitfalls
Beyond missing parentheses, macros have another classic trap: arguments used more than once can be evaluated more than once.
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main() {
int n = 4;
printf("%d\n", SQUARE(n++)); // becomes ((n++) * (n++)) -- unsafe!
return 0;
}Because SQUARE(n++) expands to ((n++) * (n++)), n is incremented twice and the result is undefined. Never pass expressions with side effects (like n++) to a macro. A real function would not have this problem.
Summary
- A macro is a
#definetext-replacement rule handled before compilation. - Object-like macros name constants; function-like macros take arguments.
- Always parenthesise every argument and the whole macro body.
#stringizes an argument;##pastes tokens together.- Macros have no type checking and can double-evaluate arguments — prefer functions when in doubt.
Frequently Asked Questions
What is a macro in C?
#define that the preprocessor replaces with a piece of text before compilation. It can be a simple constant like #define PI 3.14, or a function-like macro that takes arguments. The compiler never sees the macro name — only its replacement text.What is the difference between a macro and a function in C?
Why must function-like macros use so many parentheses?
#define SQ(x) x*x turns SQ(2+3) into 2+3*2+3 = 11, not 25. Wrapping every argument and the whole body in parentheses — ((x)*(x)) — prevents this.What do the # and ## operators do in macros?
# operator turns a macro argument into a string ("stringize"), so #x becomes "x". The ## operator glues two tokens into one ("token pasting"), so a ## b becomes ab. They are used in advanced macros for generating names and debug messages.Are macros considered bad practice in C?
const or enum, and for small operations an inline function is safer. Macros remain useful for conditional compilation and a few tasks functions cannot do, but overusing complex macros makes code hard to read and debug.