C Preprocessor
The C Preprocessor (CPP) is a separate stage in the compilation process that performs text processing on the source code before the actual compilation. Its main functions include:
- Macro expansion
- File inclusion
- Conditional compilation
- Special directive handling
The C Preprocessor is not part of the compiler, but it is a separate step in the compilation process.
In short, the C Preprocessor is just a text replacement tool. It instructs the compiler to complete the required preprocessing before the actual compilation.
We will abbreviate the C Preprocessor (C Preprocessor) as CPP.
All preprocessor commands begin with the hash symbol #. It must be the first non-whitespace character. For readability, preprocessor directives should start from the first column.
The following lists all important preprocessor directives:
| Directive | Description | Usage Example |
|---|---|---|
#define |
Define a macro (symbolic constant or function-like macro) | #define PI 3.14159#define MAX(a,b) ((a) > (b) ? (a) : (b)) |
#include |
Include a header file | #include <stdio.h>#include "myheader.h" |
#undef |
Undefine a previously defined macro | #undef PI |
#ifdef |
Compile subsequent code if the macro is defined | #ifdef DEBUGprintf("Debug infon");#endif |
#ifndef |
Compile subsequent code if the macro is not defined (often used for header guards) | #ifndef HEADER_H#define HEADER_H/* content */#endif |
#if |
Conditional compilation (can be used with the defined operator) |
#if VERSION > 2/* new version code */#endif |
#else |
Alternative branch for #if/#ifdef/#ifndef |
#ifdef WIN32/* Windows code */#else/* other systems */#endif |
#elif |
Similar to else if | #if defined(UNIX)/* Unix code */#elif defined(WIN32)/* Windows code */#endif |
#endif |
End a conditional compilation block | As shown in the examples above. |
#error |
Produce a compilation error and output a message | #if !defined(C99)#error "Requires C99 standard"#endif |
#pragma |
Compiler-specific directive (non-standard, varies between compilers) | #pragma once#pragma pack(1) |
Analyze the following examples to understand the different directives.
#define MAX_ARRAY_LENGTH 20
This directive tells the CPP to replace all occurrences of MAX_ARRAY_LENGTH with 20. Using #define to define constants enhances readability.
#include <stdio.h>#include "myheader.h"
These directives tell the CPP to get stdio.h from the system library and add its text to the current source file. The next line tells the CPP to get myheader.h from the local directory and add its content to the current source file.
#undef FILE_SIZE#define FILE_SIZE 42
This directive tells the CPP to undefine the previously defined FILE_SIZE and then define it as 42.
#ifndef MESSAGE#define MESSAGE "You wish!"#endif
This directive tells the CPP to define MESSAGE only if it has not been previously defined.
#ifdef DEBUG/* Your debugging statements here */#endif
This directive tells the CPP to process the enclosed statements if DEBUG is defined. This is useful if you pass the -DDEBUG flag to the gcc compiler during compilation. It defines DEBUG, allowing you to turn debugging on or off during compilation.
Example
#include <stdio.h>
// Define constant macros
#define PI 3.1415926
#define GREETING "Hello, World!"
// Define function-like macros (note the use of parentheses)
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// Conditional compilation example
#define DEBUG 1
int main() {
// Use constant macros
printf("Value of PI: %fn", PI);
printf("%sn", GREETING);
// Use function-like macros
int x = 5;
printf("Square of %d is: %dn", x, SQUARE(x));
printf("The larger number between 3 and 5 is: %dn", MAX(3, 5));
// Conditional compilation example
#ifdef DEBUG
printf(" Program execution reached main functionn");
#endif
// Compiler version check
#if __STDC_VERSION__ >= 201112L
printf("Using C11 standardn");
#elif __STDC_VERSION__ >= 199901L
printf("Using C99 standardn");
#else
printf("Using C89/C90 standardn");
#endif
// Error directive example (uncommenting will cause a compilation error)
// #error "This is a manually triggered error"
return 0;
}
Best Practice Recommendations
-
Macro Naming:
- Use all uppercase letters and underscores for naming macros.
- Example:
#define MAX_SIZE 100
-
Considerations for Function-like Macros:
- Enclose each parameter and the entire expression in parentheses.
- Avoid using parameters with side effects (e.g.,
SQUARE(x++)).
-
Header Guards:
#ifndef MY_HEADER_H #define MY_HEADER_H /* Header file content */ #endif -
Conditional Compilation for Debugging:
#ifdef DEBUG #define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define DEBUG_PRINT(fmt, ...) #endif -
Cross-Platform Development:
#if defined(_WIN32) // Windows-specific code #elif defined( __linux__ ) // Linux-specific code #elif defined( __APPLE__ ) // macOS-specific code #endif
ANSI C defines many macros. You can use these macros in your programming, but you cannot directly modify these predefined macros.
| Macro | Description |
|---|---|
| __DATE__ | The current date, a character constant in the format "MMM DD YYYY". |
| __TIME__ | The current time, a character constant in the format "HH:MM:SS". |
| __FILE__ | This contains the current filename, a string constant. |
| __LINE__ | This contains the current line number, a decimal constant. |
| __STDC__ | Defined as 1 when the compiler compiles to the ANSI standard. |
Let's try the following example:
Example
#include <stdio.h>
int main() {
printf("Current File: %sn", __FILE__);
printf("Compilation Date: %sn", __DATE__);
printf("Compilation Time: %sn", __TIME__);
printf("Current Line Number: %dn", __LINE__);
printf("ANSI Standard: %dn", __STDC__);
printf("n %s (Line %d) compiled on %s %sn", __FILE__, __LINE__, __DATE__, __TIME__);
return 0;
}
When the above code (in a file named test.c) is compiled and executed, it produces the following result:
Current File: predef_macros.c
Compilation Date: Jul 5 2023
Compilation Time: 14:30:45
Current Line Number: 13
ANSI Standard: 1
predef_macros.c (Line 16) compiled on Jul 5 2023 14:30:45
The C Preprocessor provides the following operators to help you create macros:
Macro Continuation Operator ()
A macro is usually written on a single line. If a macro is too long to fit on one line, the macro continuation operator () is used. For example:
#define message_for(a, b)
printf(#a " and " #b ": We love you!n")
Stringization Operator (#)
In a macro definition, when you need to convert a macro's parameter into a string constant, use the stringization operator (#). The operator has a specific parameter or parameter list used within the macro. For example:
Example
#include <stdio.h>
#define message_for(a, b)
printf(#a " and " #b ": We love you!n")
int main(void) {
message_for(Carole, Debra);
return 0;
}
When the above code is compiled and executed, it produces the following result:
Carole and Debra: We love you!
Token Pasting Operator (##)
The token pasting operator (##) within a macro definition concatenates two arguments. It allows two separate tokens in the macro definition to be merged into a single token. For example:
Example
#include <stdio.h>
#define tokenpaster(n) printf("token" #n " = %d", token##n)
int main(void) {
int token34 = 40;
tokenpaster(34);
return 0;
}
When the above code is compiled and executed, it produces the following result:
token34 = 40
How does this happen? Because this example results in the following actual output from the compiler:
printf ("token34 = %d", token34);
This example demonstrates that token##n concatenates to become token34. Here, we used both the stringization operator (#) and the token pasting operator (##).
defined() Operator
The preprocessor defined operator is used in constant expressions to determine if an identifier has been defined using #define. If the specified identifier is defined, the value is true (non-zero). If the specified identifier is not defined, the value is false (zero). The following example demonstrates the use of the defined() operator:
Example
#include <stdio.h>
#if !defined(MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void) {
printf("Here is the message: %sn", MESSAGE);
return 0;
}
When the above code is compiled and executed, it produces the following result:
Here is the message: You wish!
A powerful feature of CPP is the ability to simulate functions using parameterized macros. For example, the following code calculates the square of a number:
int square(int x) {
return x * x;
}
We can rewrite the above code using a macro as follows:
#define square(x) ((x) * (x))
Before using a macro with parameters, it must be defined using the #define directive. The parameter list is enclosed in parentheses and must immediately follow the macro name. No space is allowed between the macro name and the left parenthesis. For example:
Example
#include <stdio.h>
#define MAX(x,y) ((x)>(y) ? (x) : (y))
int main(void) {
printf("Max between 20 and 10 is %dn", MAX(10, 20));
return 0;
}
When the above code is compiled and executed, it produces the following result:
Max between 20 and 10 is 20
YouTip