When we compile a project, we expect all files to get compiled the way we want. But before even compilation, there is another program that runs prior to it — the preprocessor.
When the preprocessor runs, it scans for directives that start with the # symbol. Example: #include, #if, #ifdef, #ifndef, etc.
The syntax of these directives is not exactly like C or C++, though it might sometimes seem similar.
1. Defining Macros
-
Format:
#define identifier replacement -
Example:
#define Table_size 100
Note: there is no semicolon to terminate — it terminates by newline.
#define and #undef — to define and undefine an identifier
#define x 100
int table1[x]; // this is a C++ code line, not the preprocessor directive
// that's why we terminate it with a semicolon
#undef x
#define x 200
int table2[x];
2. Special Operators: # and ##
# (Stringizing)
The symbol # followed by a parameter name is replaced by a string literal containing the argument passed.
#define str(x) #x
So now, every time in C++ code a pattern like str(x) is seen, it will be replaced by "x" — x inside double quotes.
## (Token pasting)
The symbol ## is used to concatenate two arguments, leaving no blank space between them.
#define stick(a,b) a##b
So now in a C++ program, a line like:
stick(c,out) << "hi";
...will be interpreted as:
cout << "hi";
3. Conditional Compilation / Inclusion
#ifdef, #ifndef, #if, #endif, #else, #elif
| Directive | Behaviour |
|---|---|
#ifdef |
Checks if a macro is defined. If yes, executes the block of code below it |
#ifndef |
Checks if a macro is not defined |
#if |
Followed by a condition (like in C++ code) |
#else, #elif
|
Similar behaviour to conditional statements in C++ |
#endif |
Every #if should be ended by #endif to let the preprocessor know it's closed |
Example:
#if size > 200
#undef size
#define size 200
#elif size < 50
#undef size
#define size 50
#else
#undef size
#define size 100
#endif
(Note: this works because size must already exist as a macro — #if evaluates the macro-expanded value.)
For understanding, in C++ we can write the above logic as:
if (size > 200) size = 200;
else if (size < 50) size = 50;
else size = 100;
4. Predefined Macros
| Macro | Value |
|---|---|
__LINE__ |
Int value representing the line number being compiled |
__FILE__ |
Name of the file being compiled |
__DATE__ |
Date of compilation, in string format "Mmm dd yyyy"
|
__TIME__ |
Time of compilation, in string format "hh:mm:ss"
|
These macros are used directly in a C++ program.
Example:
#include <iostream>
int main() {
std::cout << "this is line " << __LINE__ << " of file " << __FILE__
<< "\nThis file is compiled on " << __DATE__
<< " at time " << __TIME__ << '\n';
return 0;
}
Output:
this is line 7 of file macros.cpp
This file is compiled on Jul 30 2026 at time 09:24:58
5. Header Guards
One of the main applications of conditional directives is as a guard.
If a project has two or more files that import the same header file, and they're compiled together, an error of duplicate definition may occur — since the same code or function from the header file could end up used in multiple files, and when compiled into one object file, the contents from all files get added together, causing a duplicate definition.
To avoid this, the following guard is used:
#ifndef FILE_H
#define FILE_H
// write code here
#endif
This acts as a guard and prevents multiple inclusion of the header file.
(Tip: avoid naming guards with a leading double underscore like __FILE_H — those names are reserved for the compiler/implementation. Prefer something like FILE_H instead.)
Top comments (0)