The C preprocessor scans and modifies the source code before it goes into compilation. It plays a crucial role in ensuring the code is ready for the compiler by handling various tasks. For example, comments in the source code are ignored by the C compiler, and they are removed during the preprocessing stage, before actual compilation begins. This allows the compiler to only work with the raw C code, without being distracted by the comments, which are there just for documentation and clarity for developers.
In the similar way, All Preprocessor directives in C are primarily used for code maintenance, conditional compilation, and including files. These directives are handled by the preprocessor and are not passed on to the compiler. Their purpose is to make the code more manageable, customizable, and adaptable for different environments or conditions, but they do not directly generate any machine code. In this chapter we are going to cover:
- Macros
- Nesting Macros
- Macros with parameters
- Macros with functions
- #undef
- File inclusion
- Conditional Compilation
Rules for Preprocessor Directives
Before diving into the preprocessor’s functions, let’s go over some basic rules:
- Preprocessor directives must start with a
#
. - They should not end with a semicolon.
- To continue a directive onto the next line, the current line must end with a backslash
\
. - Preprocessor directives can be written anywhere in the program.
1. Macros
A macro is a fragment of code that gets replaced by its value or definition during the preprocessing stage. Simple macro substitution is done using the #define
directive. The syntax is:
#define MACRO_NAME replacement_text
When the preprocessor encounters MACRO_NAME
in the code, it replaces it with replacement_text
.
Example
#include <stdio.h>
#define PI 3.14
int main() {
float radius = 5;
float area = PI * radius * radius;
printf("Area of the circle: %f\n", area);
return 0;
}
In this code:
#define PI 3.14
defines a macro namedPI
.- Everywhere
PI
is used in the program, the preprocessor replaces it with3.14
before compiling.
Key Points:
- No Data Type: A macro is not a variable and has no data type. It simply substitutes text.
- Global Replacement: Macros are replaced throughout the entire program.
2. Nesting in Macros
Nesting macros involves defining one macro inside another, allowing you to reuse logic or expressions.
Example
#include <stdio.h>
#define PI 3.14
#define PI_SQUARE PI * PI
int main() {
printf("square of PI is %d\n", PI_SQUARE);
return 0;
}
Here, PI_SQUARE is a nested macro that uses PI to calculate the square of PI value.
3. Macros with Parameters
A macro with parameters works like a function but is expanded during preprocessing. The arguments passed to the macro are replaced directly into the code.
Example
#include <stdio.h>
#define BIT(a) 1<<a
int main() {
printf("value: %d\n", BIT(7));
return 0;
}
BIT
is a macro that takes a as an argument, it left shifts 1 by a times.
4. Macros with Functions
Macros can act like functions but without the overhead of a function call. They simply replace code at compile time, often used for small operations where performance is crucial.
Example:
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int x = 10, y = 20;
printf("Max: %d\n", MAX(x, y));
return 0;
}
The macro MAX(a, b)
is a function-like macro that compares two numbers and returns the maximum.
5. #undef
The #undef
directive is used to undefine a macro that was previously defined using #define
. Once undefined, the macro cannot be used unless redefined.
#include <stdio.h>
#define MAX_ARR_SIZE 50
int main() {
printf("max array size is %d\n", MAX_ARR_SIZE);
#undef MAX_ARR_SIZE
printf("max array size after undefined: %d\n", MAX_ARR_SIZE);
return 0;
}
Error while compiling
error: ‘MAX_ARR_SIZE’ undeclared (first use in this function)
11 | printf("max array size after undefined: %d\n", MAX_ARR_SIZE);
| ^~~~~~~~~~~~
The macro can be redefined later if #undef is used
#include <stdio.h>
#define MAX_ARR_SIZE 50
int main() {
printf("max array size is %d\n", MAX_ARR_SIZE);
#undef MAX_ARR_SIZE
#define MAX_ARR_SIZE 10
printf("max array size after redefined: %d\n", MAX_ARR_SIZE);
return 0;
}
output
max array size is 50
max array size after redefined: 10
5. File inclusions
#include
is used to include header files or other files into your program. You can include standard or user-defined headers.
Example:
#include <stdio.h> // Standard library inclusion
#include "myheader.h" // User-defined header inclusion
int main() {
printf("Hello World\n");
return 0;
}
Files like <stdio.h>
provide standard input/output functions.
"myheader.h"
is a user-defined header that can contain custom macros or function declarations.
6. Conditional Compilation
Conditional compilation allows you to compile certain parts of code based on conditions. This is useful for platform-specific code or debugging.
#if
and #endif
Compiles code only if the condition is true.
#include <stdio.h>
#define DEBUG 1
#if DEBUG
#define LOG(x) printf("Log: %s\n", x)
#endif
int main() {
LOG("Debugging is enabled");
return 0;
}
If DEBUG
is defined as 1
, the LOG
macro is included.
#if
, #else
, #endif
Provides an alternative if the condition is false
#include <stdio.h>
#define VERSION 2
#if VERSION == 1
#define FEATURE "Version 1 features"
#else
#define FEATURE "Version 2 features"
#endif
int main() {
printf("%s\n", FEATURE);
return 0;
}
Depending on the VERSION
, different code will be compiled.
#if
, #elif
, #endif
Allows multiple conditions.
#include <stdio.h>
#define LEVEL 3
#if LEVEL == 1
#define MODE "Low"
#elif LEVEL == 2
#define MODE "Medium"
#else
#define MODE "High"
#endif
int main() {
printf("Mode: %s\n", MODE);
return 0;
}
Different LEVEL
values determine the mode.
#ifdef
and #endif
Checks if a macro is defined.
#include <stdio.h>
#define FEATURE_ENABLED
#ifdef FEATURE_ENABLED
#define MESSAGE "Feature is enabled"
#endif
int main() {
printf("%s\n", MESSAGE);
return 0;
}
MESSAGE
is only defined if FEATURE_ENABLED
is present.
#ifndef
and #endif
Checks if a macro is not defined.
#include <stdio.h>
#ifndef MAX_SIZE
#define MAX_SIZE 100
#endif
int main() {
printf("Max Size: %d\n", MAX_SIZE);
return 0;
}
MAX_SIZE
is defined only if it hasn’t been defined before.
Summary
#define
:
Used to define macros. A macro is a fragment of code that gets replaced throughout the code wherever it is referenced.
#if
:
Used for conditional compilation. It allows compiling specific code only if a condition is true.
#else
:
Works with #if
or #ifdef
to specify alternative code to be compiled if the previous condition is false.
#elif
:
Stands for “else if.” It combines #else
and #if
to check another condition if the previous one is false.
#endif
:
Closes an #if
, #ifdef
, or #ifndef
block.
#include
:
Used to include the contents of a file into the current file, typically used for including header files.
#ifdef
:
Used to check if a macro is defined. If the macro is defined, the code inside the block will be compiled.
#ifndef
:
The opposite of #ifdef
. It checks if a macro is not defined.
#undef
:
Used to undefine a macro, which cancels its previous definition.