The C Preprocessor is the first stage in the compilation pipeline. It is not part of the C language itself but a separate program that performs simple text-based manipulations on the source code before the code is handed off to the compiler. All preprocessor directives begin with the pound symbol (#).
1. Macro Substitution (#define)
The #define directive is used to define a macro, which is a symbolic name (constant) or a snippet of code that the preprocessor replaces with its defined value throughout the source file.
A. Constant Macros
Defines a symbolic constant. This is similar to a const variable but is handled before compilation.
Syntax: #define MACRO_NAME replacement_text
#define PI 3.14159
#define MAX_SIZE 100
B. Function-like Macros
Defines a macro that behaves like a function, accepting arguments. This is often used for simple, speed-critical operations, as it avoids the overhead of a function call.
// Defines a macro to calculate the square of a number
#define SQUARE(x) ((x) * (x))
int result = SQUARE(5); // Preprocessed to: int result = ((5) * (5));
Warning: Always use parentheses around macro arguments (like
(x)) and the entire definition (like((x) * (x))) to prevent unexpected behavior due to operator precedence issues during text substitution.
2. File Inclusion (#include)
The #include directive instructs the preprocessor to replace the directive with the entire contents of the specified file.
| Syntax | Use Case | Mechanism |
#include <filename> | Standard library headers (e.g., <stdio.h>). | The preprocessor searches in the standard system directories. |
#include "filename" | User-defined headers (e.g., "my_header.h"). | The preprocessor searches in the current directory first. |
3. Conditional Compilation
Conditional compilation directives allow you to include or exclude parts of the source code based on predefined conditions. This is essential for writing platform-dependent code or preventing header files from being included multiple times.
| Directive | Purpose |
#ifdef | Check if a macro is defined. |
#ifndef | Check if a macro is not defined (common for header guards). |
#if | Evaluates a constant integer expression. |
#else | Provides an alternative block if the preceding #if, #ifdef, or #ifndef condition is false. |
#elif | Else if (combines #else and #if). |
#endif | Marks the end of a conditional compilation block. |
A. Header Guards (#ifndef)
This prevents the same header file from being included multiple times in a project, which would cause compilation errors due to duplicate definitions.
// my_header.h
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Code definitions go here
#endif // MY_HEADER_H
B. Platform-Specific Code (#ifdef)
Use this to conditionally compile code based on the operating system or environment.
#ifdef _WIN32 // Macro defined by the compiler for Windows
printf("Running on Windows.\n");
#elif __linux__ // Macro defined by the compiler for Linux
printf("Running on Linux.\n");
#else
printf("Running on an unknown OS.\n");
#endif
4. Other Directives
| Directive | Purpose |
#undef | Undefines a previously defined macro. |
#pragma | Provides special instructions to the compiler (implementation-defined). |
#error | Generates a fatal compilation error with a specific message. |
