C Preprocessor and Macros
The C preprocessor is a step that runs before the compiler translates your program. It handles instructions that begin with #, such as including header files, replacing macros, and choosing which code should be compiled.
Preprocessor commands are not normal C statements. They are processed as text-based instructions before the compiler checks types and syntax in the final expanded code.
Common Preprocessor Directives
A preprocessor directive starts with # at the beginning of a logical line. You have already used #include to bring in standard library declarations.
| Directive | Purpose |
|---|---|
#include |
Copies declarations from a header file into the current file. |
#define |
Creates a macro name that the preprocessor replaces before compiling. |
#if, #ifdef, #ifndef |
Includes or excludes code based on preprocessor conditions. |
#endif |
Ends a conditional preprocessor block. |
Object-like Macros
An object-like macro is a name that is replaced with a value or text. It is often used for constants that should be easy to change in one place.
#include <stdio.h>
#define MAX_USERS 3
#define APP_NAME "Course Tracker"
int main(void)
{
printf("%s\n", APP_NAME);
printf("Maximum users: %d\n", MAX_USERS);
return 0;
}
Output:
Course Tracker
Maximum users: 3
Before compilation, the preprocessor replaces APP_NAME with "Course Tracker" and MAX_USERS with 3. By convention, macro names are usually written in uppercase so they stand out from variables.
Function-like Macros
A function-like macro accepts arguments and expands into code. It can look like a function call, but it is still text substitution. For simple expressions, wrap each argument and the whole replacement expression in parentheses.
#include <stdio.h>
#define SQUARE(n) ((n) * (n))
#define BIGGER(a, b) ((a) > (b) ? (a) : (b))
int main(void)
{
int x = 4;
int y = 7;
printf("square: %d\n", SQUARE(x + 1));
printf("bigger: %d\n", BIGGER(x, y));
return 0;
}
Output:
square: 25
bigger: 7
The parentheses in SQUARE are important. Without them, SQUARE(x + 1) could expand in a way that changes the order of operations.
Macro Pitfalls
Macros are powerful, but they do not behave exactly like functions. A macro argument may be used more than once after expansion, so avoid passing expressions with side effects such as i++.
Prefer normal functions when you need type checking, complex logic, or debugging support. Use macros for simple constants, compile-time choices, and very small expression replacements.
Conditional Compilation
Conditional compilation lets you include code only when a preprocessor condition is true. This is useful for debug output, platform-specific code, and optional features.
#include <stdio.h>
#define ENABLE_LOG 1
#if ENABLE_LOG
#define LOG(message) printf("LOG: %s\n", message)
#else
#define LOG(message) ((void) 0)
#endif
int main(void)
{
printf("Program started\n");
LOG("loading settings");
printf("Program finished\n");
return 0;
}
Output:
Program started
LOG: loading settings
Program finished
Because ENABLE_LOG is 1, the first version of LOG is used. If you changed it to 0, the logging macro would expand to ((void) 0), which does nothing.
Header Guards
Large C programs often split declarations into header files. A header guard prevents the same header content from being included more than once in a single source file.
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
#endif
This pattern means: if MATH_UTILS_H has not been defined yet, define it and include the declarations. If the header is included again later, the declarations inside the guard are skipped.
Best Practices
- Use uppercase names for macros, such as
BUFFER_SIZE. - Put parentheses around macro arguments in expression macros.
- Avoid side effects inside macro arguments, such as
count++. - Use
constvariables or functions when runtime type checking matters. - Use conditional compilation for code that should be included or removed before compiling.
The key idea is that the preprocessor prepares your source text before C compilation begins, so macros are useful but should stay simple and predictable.
