Preprocessor Directives in C++
Overview
Pre-processing in C++ means to execute/process the program file before the execution of the main program. As part of the initial step in the compilation or translation of a C++ program, the pre-processor updates the source program file according to the preprocessor directives in C++. All the statements starting with the # (hash) symbol are known as Preprocessor Directives in C++. Pre-processor instructions are executed in the first phase of the compilation process and it produces an intermediate file with the .i extension. In source code, we generally write all the preprocessor directives like file inclusion and macros definition outside the main() function at the top of our C++ program.
What are Directives?
All the statements starting with the # (hash) symbol are known as preprocessor directives in C++. Now, like a coach instructs his/her students to perform certain tasks to improve/her performance, the directives instruct the preprocessor to perform certain tasks to improve the program's performance/capabilities.
For Example : The pre-processor can import the contents of other program files into the source code file, and expand the macros (macro is some constant value or an expression with a name that can be used throughout a C++ program), conditionally compile a code, etc. Every directive is a one-line long command which contains the following:
- A # (hash) symbol (All the preprocessor directives in C++ start with the # (hash) symbol).
- A pre-processor instruction after the # (hash) symbol. For example, #include, #define, #ifdef, #elif, #error, #pragma etc.
- Arguments are based on the type of the directive. For example, <iostream> is argument for #include, PI 3.14 are arguments for #define directive.
Example: #include<iostream>, #define PI 3.14, #ifdef PI etc.
Note:
- module and import instructions are added to C++ preprocessor since the release of the C++20 the preprocessor directives in C++.
Source File Translation Capabilities
Source file translation capabilities mean that the pre-processor can manipulate the source code file using the preprocessor commands in C++. It comes from a translation unit that essentially processes the source code file by the preprocessor. During the pre-processing, Header files indicated in #include directives are merged with the source files, parts of code within #ifndef directives may be compiled based on the argument, and macros defined by #define are expanded. The pre-processor can translate source code files in various ways as given below:
- Including additional files (like header files) that are controlled by the #include directive.
- Replacing the macros with a constant value or an expression value using the #define directive.
- Conditionally compiling the parts of our source code using the #ifdef, #elif, #else etc. directives.
- Causing an error using the #error directive.
- Line number and the file name manipulation using the #line directive.
- Manipulating implementation behavior like turning some features of the code on/off using the pragma directives.
The #define Pre-processor
- #define is a pre-processor directive in C++ that is used for defining macros in a C++ program.
- A Macro is some constant value or an expression with a name that can be used throughout in a C++ program declared using the #define directive.
- #define directives are also known as Macro directives.
- Whenever a #define directive is encountered in a C++ program, the defined macros name replaces it with some defined constant value or an expression during the initial step of the compilation process.
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: In the above C++ program, we have defined the PI value to 3.14 using the #define directive. We have used the value of PI in the main() program to find and print the area of circle (PI * r * r) in the output.
Types of Preprocessor Directives in C++
There are various types of preprocessor directives in C++ which can be used in a C++ program : macros, file inclusion, conditional compilation, line control directives etc. Let's see the definition and example of each directive below.
1. Macros in C++
i. Pre-defined C++ Macros
Pre-defined macros in C++ are ones that are already defined by the compiler in comparison to the macros that are defined by the user in a C++ program itself, the user can't re-define these macros in C++ program. We can use pre-defined macros directly in a C++ program.
Pre-defined Macros | Definition |
---|---|
__cplusplus | It is an integer literal value that represents the C++ compiler version and it is defined by the compatible C++ compilers during the compilation of a C++ program. For example, value represents the 2017 C++ version. |
__DATE__ | It is a constant length string literal displayed in Mmm dd yyyy format and it is replaced by the date at which our source code file is compiled. |
__TIME__ | It is a character string literal displayed in hh:mm:ss format and it is replaced by the time at which our source code file is compiled. |
__FILE__ | It is also character string literal which is replaced by the source code file path/name from where it is stored in the computer, during the pre-processing. |
__LINE__ | It is an integer literal value and this directive is replaced by the line number in the source code where it is encountered by the compiler during pre-processing. |
__STDC__ | To validate the compiler version __STDC__ macro is used. It usually has the value 1, indicating that the compiler complies with ISO Standard C. Otherwise, it is undefined. |
__STDC_HOSTED__ | If the compiler has a hosted implementation that provides the whole needed standard libraries in a C++ program, its value is replaced by during pre-processing. Otherwise, is used. |
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: In the above C++ program, we have printed the values of all the commonly used pre-defined macros. We have printed C++ version, Date, Time, File name, Line number, STDC and STDC_HOSTED values using the above pre-defined macros in the table.
Macros with Arguments
It is some constant value or an expression that can be defined explicitly by the user using the #define directive in a C++ Program. The constants or the expressions will be replaced during pre-processing by the respective values assigned at the definition of the macros.
Examples:
- Defining a Value
- Defining an Expression
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: We have defined an expression AREA(l, b) to return the product (l * b) using the #define directive. We have used the expression AREA(l, b) in the main() function to find and print the area of rectangle (l * b) in the output.
2. File Inclusion
File inclusion is a pre-processor directive in C++ which is used to include content of some other file into the source file. It can be a header file or some user defined file also. Let's see how we can include other file into our source file.
i. Header File or Standard Files
It is adding defined as content of a header file into our C++ Program and it can be done using the #include command.
Examples:
- Including input output stream header file
or
When you include the <iostream> header file in the source code, you can use any of its input output stream functions/objects like cout, cin etc. in the program.
- Including all the standard library functions through bits/stdc++.h header File
When you include the bits/stdc++.h header file in the source code, you can use any of the standard library header files in a C++ program like <iostream>, <cmath>, <vector>, <algorithm> etc.
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
ii. User Defined Files
When a C++ program becomes too large, it is a good practice to break it into smaller files and include them in our program as required, it increases the flexibility and modularity of our program. These files are created by the user, so these are known as user defined files. These files can be included in our program using the very similar syntax as mentioned above. Let's see the syntax :
Note: You have insure that the user defined file (for example process.cpp) is present in the same folder as the source file (example solution.cpp).
Example C++ Program:
- addition.cpp
- solution.cpp
Output:
Explanation: This is a very small example of user-defined file inclusion. We have included an addition.cpp file in our main solution.cpp file. We are using the add() function from the addition.cpp file to calculate the sum of two numbers in the solution.cpp file.
3. Conditional Compilation
In conditional compilation, we can execute or skip a piece of code on the condition, if the macro passed as argument is defined or not (macro is a constant value or an expression defined using #define). Conditional Compilation is performed using the commands like #ifdef, #endif, #ifndef, #if, #else and #elif in a C++ Program. We pass a macro as an argument to the #ifdef directive to check if the macro is defined or not and based on this the code below the #ifdef directive will be executed.
Example C++ Program:
Printing age if macro is defined, else printing not defined
Check and run this program using InterviewBit IDE.
Output :
Explanation: We have defined the AGE macro and commented the definition of PI macro. In the main() function, if PI is defined, we print the value of PI, else if AGE is defined, we print the value of AGE, else we print Not defined.
4. Other Types of Directives
i. #undef Directive
To undefine an existing macro, we use the #undef directive. The #undef directive is often used in combination with the #define directive to specify a section in a source code where a macro holds a specific meaning.
Syntax:
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: We have defined a PI macro with value . In the main() function, we have used #undef PI to undefine the PI macro. We can see from the Compilation Error that PI is not defined in the scope.
ii. #pragma Directive
pragma directives provide some additional information to the compatible compilers (It is not supported by GCC compilers but we can use supported compilers like Intel C++ Compiler, IBM XL C/C++ etc.) while compiling a C/C++ Program. The #pragma directives allow some specific compilers to provide machine and operating system specific capabilities while retaining general C and C++ language compatibility. Few of the pragma directives are discussed below:
- #pragma startup and #pragma exit
- #pragma startup is used to run a function before the execution of the main() function.
- #pragma exit is used to run a function when the execution of the main() function finishes.
Note: pragma directives are not supported by GCC compilers so the output may differ. Supported compilers are Intel C++ Compiler, IBM XL C/C++ etc.
To execute the pragma functionalities in GCC compiler, we will be using GCC specific syntax i.e. __attribute__((constructor)) and __attribute__((destructor)), which executes before the main() and after the main() respectively (These are not macros or any directives, these are some specific objects defined in the GCC compiler itself).
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: We are using GCC specific syntax i.e. __attribute__((constructor)) init() and __attribute__((destructor)) end() to replicate pragma directives **#pragma start init, #pragma exit end. So, init() will execute before main() function and end() will execute after the execution of the main() function.
- #pragma warn Directive
#pragma warn directive helps in suppressing the warning messages that appear during the compilation process. When we have large programs and we want to fix all of the errors in the program before looking at the warnings, we may use this to hide all warnings and focus on the errors only and then by making little syntactic adjustments, we can make the warnings visible once again. Now, let's see the types of warning that pragma warn can suppress using different flags:
a. #pragma warn -rvl: This directive hides the warnings that are produced when a function that should return a value fails to do so. b. #pragma warn -par: This directive hides the warnings that are produced when the function parameters are not used inside the function body. c. #pragma warn -rch: This directive hides the warnings that are produced when a code is unreachable. Such as, when we write some code after a return statement in the function it becomes unreachable and throws a warning.
The signs before a warn flag means:
- '+' is used to turn on warnings in the output, if any.
- '-' is used to turn off the warnings in the output, if any.
Example C++ Program:
Check and run this program using InterviewBit IDE.
Note: #pragma warn directive is not supported in GCC compiler, so it is compiler dependent and you may see the warnings.
Output:
Explanation: #pragma warn -rvl is used to suppress the no return value warning, #pragma warn -par is used to suppress the parameter not used warning and #pragma warn -rch is used to suppress the unreachable code warning in our program.
v. #error
If the compiler detects the #error pre-processor directive in C++ program during the pre-processing phase, it terminates the compilation and publishes the tokens as an error on the standard output. It is especially effective in combination with #if/#elif/#else to prevent compilation if a condition is not met.
Syntax:
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: #error No Code Found! raises an error during the execution of the main() function with a No Code Found! message.
vi. #line
The compiler/translator normally uses #line directive to manipulate the line number and file name during the compilation of a C++ program. When the compiler encounters a #line directive, it instructs the pre-processor to change the compiler’s reported line number and filename values to a specified line number and filename.
Syntax:
Example C++ Program:
Check and run this program using InterviewBit IDE.
Output:
Explanation: #line 10 directive at line changes the next line number for compiler to and #line 20 directive at line (line 10 in code) changes the next line number to and file name to scaler_topics.cpp.
The # and ## Operators
The # and ## operators are preprocessor operators. Let's see the definition of each operator with an example C++ program below:
-
# operator : This operator wraps the appropriate arguments passed in the corresponding macros in double quotation marks. It is also known as the Stringizing Operator, which converts the input it precedes into a quoted string.
-
## operator : This operator allows passed tokens or arguments to be concatenated/merged to create new token/variable. For example, into a single variable . When extending macros, it's common to combine two tokens into one. It is also known as Token-Pasting Operator.
Example C++ Program representing the use of # and ##:
Check and run this program using InterviewBit IDE.
Output:
Explanation: stringer() macro converts cout<<stringer(scaler_topics); using the Stringizing operator '#' into cout<<"scaler_topics"; (a string), while mix(a, b) concatenates (a, b) into one ab variable using Token-pasting operator '##'.
Conclusion
- C++ Pre-processor commands are executed as the initial step in the compilation of a program.
- There are various types of preprocessor directives in C++, such as macros, file inclusion, conditional compilation, error, pragma and others.
- The pre-processor can import the contents of other program files into the source code file, expand the macros, conditionally compile a code etc.
- #define directive is used to declare a macro i.e. a constant value or expression with a name that can be used throughout the program.
- #include directive is used to include the content of some other file into our C++ program.
- # is Stringizing operator and is Token-pasting operator.