π Today, I am going to consider one of the most essential but least common topics in C/C++ world - Preprocessors and Macros.
Preprocessors are programs that process the source code before compilation. There are a number of steps between writing and executing a program in C. Let us have a look at these steps before we actually start familiarizing:
- Source code written by programmers is stored in a file
program.c
. - It is then processed by preprocessors and an expanded source code file is generated
program.i
. - Expanded file is compiled by the compiler and an object code file is generated
program.obj
. - The linker links this object code file to the object code of the library functions to generate the executable file
program.exe
.
Preprocessor Directives
Preprocessor programs provide preprocessor directives that tell the compiler to preprocess the source code before compiling. All of these preprocessor directives begin with a #
(hash) symbol.
βοΈ Remember that the
#
symbol only provides a path to the preprocessor, and a command such asinclude
is processed by the preprocessor program. For example,#include
will include the code or content of the specified file in your program.
The following table lists all directives:
There are 3 Main Types of Preprocessor Directives:
- Macros
- File Inclusion
- Conditional Compilation
Let's now consider each of these directives in detail with many subtleties.
1. Macros
A macro is a piece of code in a program that is replaced by the value of the macro. Macro is defined by #define directive. Whenever a macro name is encountered by the compiler, it replaces the name with the definition of the macro. We use it always, you know, when we declare static arrays like below:
#include <stdio.h>
#define MAX 100 // Macro
int main()
{
int array[MAX]; // Declaring array
printf("The value of MAX" is "%d", MAX);
return 0;
}
However, there are many other types of macros that probably you have not used yet.
1.1. Object-like Macros
An object-like macro is a simple identifier that will be replaced by a code fragment. It is called object-like because it looks like an object in code that uses it. It is popularly used to replace a symbolic name with numerical/variable represented as constant. Just now we illustrated the example above.
1.2. Chain Macros
Macros inside macros are termed as chain macros. In chain macros first of all parent macro is expanded then the child macro is expanded. Below is the illustration of a Chain Macro:
#include <stdio.h>
#define INSTAGRAM FOLLOWERS
#define FOLLOWERS 500
int main()
{
printf("Cristiano Ronaldo has "%dM" followers on Instagram", INSTAGRAM);
return 0;
}
// Output: Cristiano Ronaldo has 500M followers on Instagram
1.3. Multi-line Macros
An object-like macro could have a multi-line. So to create a multi-line macro you have to use backslash-newline. Below is the illustration of multiline macros:
#include <stdio.h>
#define ELE 1, \
2, \
3
int main()
{
int arr[] = { ELE };
printf("Elements of Array are:\n");
for (int i = 0; i < 3; i++) {
printf("%d ", arr[i]);
}
return 0;
}
// Output: Elements of Array are: 1 2 3
1.4. Function-like Macros
These macros are the same as a function call. It replaces the entire code instead of a function name. Pair of parentheses immediately after the macro name is necessary. If we put a space between the macro name and the parentheses in the macro definition, then the macro will not work. A function-like macro is only lengthened if and only if its name appears with a pair of parentheses after it. If we donβt do this, the function pointer will get the address of the real function and lead to a syntax error.
Below is the illustration of function-like macros:
#include <stdio.h>
#define min(a, b) (((a) < (b)) ? (a) : (b))
int main()
{
int a = 18;
int b = 76;
printf("Minimum value between %d and %d is %d\n", a, b, min(a, b));
return 0;
}
// Output: Minimum value between 18 and 76 is 18
2. File Inclusion
This type of preprocessor directive tells the compiler to include a file in the source code program. The #include
preprocessor directive is used to include the header files in the C/C++ program. There are 2 types of files that can be included by developer:
1. Standard Header Files
The standard header files contain definitions of pre-defined functions like printf()
, scanf()
, etc. These files must be included to work with these functions.
#include <file_name>
// < and > brackets tell the compiler to look for standard directory
2. User-defined Header Files
When a program becomes very large, it is a good practice to divide it into smaller files and include them whenever needed. These types of files are user-defined header files. These files can be included as:
#include "file_name"
// The double quotes ( β β ) tell the compiler to search for the header file in the source fileβs directory.
3. Conditional Compilation
It is a type of directive that helps to compile a specific portion of the program or to skip the compilation of some specific part of the program based on some conditions. There are the following preprocessor directives that are used to insert conditional code:
-
#if
Directive -
#ifdef
Directive -
#ifndef
Directive -
#else
Directive -
#elif
Directive -
#endif
Directive
Syntax:
#ifdef macro_name
statement1;
statement2;
statement3;
.
.
.
statementN;
#endif
If the macro with the name macro_name
is defined, then the block of statements will execute normally, but if it is not defined, the compiler will simply skip this block of statements.
Actually, there is 2 more derivatives are available, but as they are not commonly used and #pragma
is not even supported by GCC compilers, I think there is no point to show them.
π Interesting Facts
That's all about Preprocessors and Macros. Now, let's have fun by learning some gripping facts about them.
1οΈβ£ Macros can take function like arguments, the arguments are not checked for data type. For example, the following macro INCREMENT(x)
can be used for x
of any data type:
#include <stdio.h>
#define INCREMENT(x) ++x
int main()
{
char* ptr = "Embedded";
int x = 10;
printf("%s ", INCREMENT(ptr)); // pointer to string is moved by 1 character
printf("%d", INCREMENT(x));
return 0;
}
// Output: mbedded 11
2οΈβ£ The macro arguments are not evaluated before macro expansion. For example, consider the following program:
#include <stdio.h>
#define MULTIPLY(a, b) a* b
int main()
{
// The macro is expanded as 2 + 3 * 3 + 5, not as 5*8
printf("%d", MULTIPLY(2 + 3, 3 + 5));
return 0;
}
// Output: 16
This problem can be solved as shown below:
#include <stdio.h>
#define MULTIPLY(a, b) (a) * (b)
int main()
{
// The macro is expanded as (2 + 3) * (3 + 5), as 5*8
printf("%d", MULTIPLY(2 + 3, 3 + 5));
return 0;
}
// Output: 40
3οΈβ£ The tokens passed to macros can be concatenated using operator ##
called Token-Pasting operator:
#include <stdio.h>
#define merge(a, b) a##b
int main()
{
printf("%d ", merge(12, 34));
}
// Output: 1234
4οΈβ£ A token passed to macro can be converted to a string literal by using #
before it:
#include <stdio.h>
#define get(a) #a
int main()
{
// Memati is changed to "Memati"
printf("%s", get(Memati)); // Note: Memati is not a string here!
}
Output: Memati
5οΈβ£ The macros with arguments should be avoided as they cause problems sometimes. And Inline functions should be preferred as there is type checking parameter evaluation in inline functions. From C99 onward, inline functions are supported by C language also.
For example consider the following program. From first look the output seems to be 1, but it produces 36 as output.
#include <stdio.h>
#define square(x) x* x
int main()
{
// Expanded as 36/6*6
int x = 36 / square(6);
printf("%d", x);
return 0;
}
// Output: 36
But we can write this code as follows to get the expected result:
#include <stdio.h>
#define square(x) (x * x)
int main()
{
// Expanded as 36/(6*6)
int x = 36 / square(6);
printf("%d", x);
return 0;
}
// Output: 1
6οΈβ£ There are some standard macros which can be used to print program file __FILE__
, Date of compilation __DATE__
, Time of compilation __TIME__
and Line Number in C code __LINE__
. Below is an example:
#include <stdio.h>
int main()
{
printf("Current File :%s\n", __FILE__);
printf("Current Date :%s\n", __DATE__);
printf("Current Time :%s\n", __TIME__);
printf("Line Number :%d\n", __LINE__);
return 0;
}
// Output: Current File :/usr/share/IDE_PROGRAMS/C/other/081c548d50135ed88cfa0296159b05ca/081c548d50135ed88cfa0296159b05ca.c
Current Date :Sep 4 2019
Current Time :10:17:43
Line Number :8
Today, we completed discussing about Preprocessors and Macros in C. It turns out that although pieces of code with #s and <>s seems "negligible", they play really fundamental role in any C program.
The only one very vital topic is lasting: Bit Manipulation!
π That's it for now. Thank you!
Top comments (1)
Nice article!!!
Worth mentioning is that we can provide a flag to compiler to stop after preprocessor stage, so we can see what's happening there.
We can use:
The output is in the form of preprocessed source code.