THE PREPROCESSOR
Definition: The preprocessor is a program that processes the source code
before it passes through the compiler. It operates under the control of what
is known as preprocessor command lines or directives. Preprocessor
directives are placed in the source program before the main line. Before the
source code passes through the compiler, it is examined by the
preprocessor for any preprocessor directives. If there are any , appropriate
actions ( as per the directives) are taken and then the source program is
handed over to the compiler.
Syntax Rules for preprocessor directives :
They all begin with the symbol # in column one and do not require a
semicolon at the end.
List of directives :
All directives can be divided into three categories :
1. Macro substitution directives
2. File inclusion directives
3. Compiler control directives
Macro substitution :
Macro substitution is a process where a macro in the program is replaced
by numbers or expressions, or actual strings.
Syntax:
#define macro_name value
where:
macro_name → The name of the macro (a valid C identifier).
value → can be a number or an expression, or a string.
Note: If the above statement is included in the program at the beginning,
then all occurrences of the identifier are replaced with the value specified.
- There must be at least one blank space between the identifier and value.
There are different forms of macro substitutions:
1. Simple macro substitution
2. Argumented macro substitution
3. Nested macro substitution
Simple Macro substitution:
These are basically used to define constants. For example,
#define COUNT 100
#define PI 3.14
#define CAPITAL “DELHI”
All the occurrences of the above macros will be replaced by their respective
values.
Exception: Macro inside a string (quotation marks) does not get replaced.
For example,
#define M 5
total = M * value;
printf (“M = %d\n”, M);
These two lines would be changed during preprocessing as follows:
total = 5 * value;
printf(“M = %d*\n”, 5);
Note: It is a convention to write all macros in capital letters to identify
them as symbolic constants.
A macro definition can include expressions as well. For example,
#define AREA 5 * 23.5
#define SIZE sizeof(int) * 4
But care should be taken to prevent an unexpected order of
evaluation. For example,
#define D 45 - 22
#define A 78 + 32
Consider the evaluation of the equation
ratio = D/A;
The result of the preprocessor ‘s substitution for D and A is:
ratio = 45 - 22 / 78 + 32;
This is certainly not what we wanted. What we wanted was
ratio = (45 - 22) / (78 + 32);
So, this can be obtained by using parentheses around the strings as shown
below:
#define D (45 - 22)
#define A (78 + 32)
Note: It is a wise practice to use parentheses for expressions used in
macro definition.
The preprocessor performs a literal text substitution whenever the
defined name occurs. That's why we cannot use a semicolon to
terminate the #define statement.
More examples:
#define TEST if(x>y)
#define AND
#define PRINT printf(“Good.\n”);
Now if write as follows in our source code:
TEST AND PRINT
The preprocessor would translate the above line to:
if(x>y) printf(“Good\n”);
Macros with arguments:
Syntax:
#define MACRO_NAME(parameter1, parameter2, ...) value
Definition of macro call: The usage of macros with arguments in code is
known as a macro call
Examples:
#define CUBE(x) ( (x) *(x) *(x) )
In our source code if we have,
volume = CUBE(side);
This will be expanded by preprocessor as :
volume = ( (side) *(side) *(side) );
If the statement is like:
volume = CUBE (a+b);
This will expand to:
volume = ( (a+b) *(a+b) *(a+b) );
More examples:
#define PRINT(variable, format) printf (“variable = %format\n”,
variable)
can be called-in by
PRINT (price *quantity, f);
The preprocessor will expand it as follows:
printf (“price * quantity = %f\n”, price * quantity);
Nesting of macros:
Placement of one macro in the definition of another macro
Examples:
If there is a statement as
power = SIXTH(x);
In our source code, then it will be expanded as follows:
(CUBE(x) * CUBE(x))
This will be further expanded to:
( ( SQUARE (x) * (x) ) * ( SQUARE (x) * (x) ) )
Since SQUARE (x) is still a macro, it is further expanded to
( ( ( (x) *(x) ) *(x) )*( ( (x) *(x) ) *(x) ) )
which is finally evaluated as x6.
Macros can also be used as parameters of other macros. For
example,
#define MAX(M, N) ( ( ( M) > (N) ) ? (M) : (N) )
Macro calls can be nested
#define HALF(x) ( ( x) / 2.0)
#define Y HALF(HALF(x) )
So now if we use the macro, Y in our source code
printf(“Y: % .2f”, Y);
then this will result in a nested macro call
HALF(HALF(x))
It will expand as follows:
HALF(HALF(x)) → HALF((x) / 2.0) → ((x) / 2.0) / 2.0
Again, given the definition of MAX(a, b)
#define MAX(a, b) ( ( ( a) > (b) ) ? (a) : (b) )
we can use the following nested call to give the maximum of three values x,
y, and z:
MAX(x, MAX(y, z) )
This expands as:
MAX(x, ((y) > (z) ? (y) : (z)))
Here, MAX(y, z) is evaluated first, and its result is then used as an
argument to MAX(x, result).
Undefining a macro:
#undef macro_name ;
This is useful when we want to restrict the definition only to a particular part
of the program.
Example:
#include <stdio.h>
#define VALUE 100
int main() {
printf("Before #undef, VALUE: %d\n", VALUE);
#undef VALUE
printf("After #undef, VALUE: %d\n", VALUE);
return 0;
}
Output:
error: 'VALUE' undeclared (first use in this function)
11 | printf("After #undef, VALUE: %d\n", VALUE);
File inclusion
An external file containing functions or macro definitions can be included as
a part of a program so that we need not rewrite those functions or macro
definitions. This is achieved by the preprocessor directive
#include “filename”
where filename is the name of the file containing the required definitions or
functions.
At this point, the preprocessor inserts the entire contents of filename into
the source code of the program.
When the filename is included within the double quotation marks, the
search for the file is made first in the current directory and then in the
standard directories.
Example:
Create a Header File (myheader.h)
This file contains function declarations.
myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
void greet();
int add(int a, int b);
#endif
Create a Separate .c File (myfunctions.c) This file contains the function
definitions.
myfunctions.c
#include <stdio.h>
#include "myheader.h"
void greet() {
printf("Hello, Welcome to Turbo\n");
}
int add(int a, int b) {
return a + b;
}
Create the Main Program (main.c)
This file will include the header file and call functions from myfunctions.c.
main.c
#include <stdio.h>
#include "myheader.h"
int main() {
greet();
int sum = add(10, 20);
printf("Sum: %d\n", sum);
return 0;
}
Output:
Hello, Welcome to Turbo
Sum: 30
Alternatively, this directive can take the form
#include<filename>
without double quotation marks. In this case, the file is searched only in
the standard directories.
Nesting of included files is allowed. That is, an included file can
include other files. However, a file cannot include itself.
Example:
Case 1: With Include Guards (Correct Way)
File1.h
#ifndef FILE1_H
#define FILE1_H
#include<stdio.h>
#include "file2.h"
void func1() {
printf("This is func1 in file1.h\n");
func2();
}
#endif
File2.h
#ifndef FILE2_H
#define FILE2_H
#include <stdio.h>
void func2() {
printf("This is func2 in file2.h\n");
}
#endif
main.c
#include "file1.h"
#include "file2.h"
int main() {
func1();
return 0;
}
Expanded Code After Preprocessing
Contents of stdio.h included
void func2() {
printf("This is func2 in file2.h\n");
}
// From file1.h
void func1() {
printf("This is func1 in file1.h\n");
func2();
}
int main() {
func1();
return 0;
}
Everything compiles fine because:
FILE2_H prevents file2.h from being included twice.
func2() is defined only once
Case 2: Without Include Guards (Problematic)
File1.h
#include "file2.h"
#include<stdio.h>
void func1() {
printf("This is func1 in file1.h\n");
func2();
}
File2.h (No guards)
#include <stdio.h>
void func2() {
printf("This is func2 in file2.h\n");
}
main.c
#include "file1.h"
#include "file2.h"
int main() {
func1();
return 0;
}
Expanded Code After Preprocessing
void func2() {
printf("This is func2 in file2.h\n");
}
void func1() {
printf("This is func1 in file1.h\n");
func2();
}
void func2() {
printf("This is func2 in file2.h\n");
}
int main() {
func1();
return 0;
}
Compilation Error:
error: redefinition of ‘func2’
Because:
func2() is defined twice
C does not allow a function to be defined more than once
Note: If an included file is not found, an error is reported and
compilation is terminated.
We can include .c files in our program using #include "filename.c". This is
useful when we want to split our code into multiple files but don’t want to
create a separate object file manually.
When we write a large program, it's a good practice to split the code into
multiple files for better organization. Normally, when we split code into
multiple .c files, we need to compile each .c file separately into an
object file (.obj ) and then link them together to create the final
executable.
However, including a .c file using #include "filename.c" directly
in another .c file makes the compiler treat it as if all the code is written in
a single file. This means:
● We don’t have to manually compile multiple .c files separately.
● The included .c file is compiled along with the main file in one
go.
When we include a .c file inside main.c using #include , the compiler treats
it as if the contents of the .c file were copied directly into main.c. This
means:
- No separate compilation is needed.
- No explicit linking step is required.
- Only main.c needs to be compiled and run.
Example:
Create stats.c (with a Macro Definition)
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int sum(int a, int b) {
return a + b;
}
float average(int a, int b) {
return sum(a, b) / 2.0;
}
Create main.c
#include <stdio.h>
#include "stats.c"
int main() {
int x = 10, y = 20;
printf("Sum: %d\n", sum(x, y));
printf("Average: %.2f\n", average (x, y));
printf("Max: %d\n", MAX(x, y));
return 0;
}
Compiler Control Directives
Need for Compiler control directives :
1. Conditional Compilation
Allows selective compilation of code based on specific conditions.
Example: Enabling debugging code only in development.
#ifdef DEBUG
printf("Debug mode is ON\n");
#endif
2. Preventing Multiple Inclusions
Ensures a header file is included only once, preventing redefinition errors.
Example :
#ifndef HEADER_H
#define HEADER_H
// Code inside header file
#endif
Definition: Conditional compilation directives in C are preprocessor
directives that enable the inclusion or exclusion of specific code segments
based on certain conditions. These directives are evaluated during the
preprocessing phase, before the actual compilation of the program.
Here are the key conditional compilation directives in C:
#if: This directive tests if a certain condition is true. If the condition
evaluates to true, the compiler includes the code between #if and the next
#endif or #else, or #elif directive.
#ifdef: This checks if a macro is defined. If the macro is defined, the code
following #ifdef up to #endif, or #else, or #elif is compiled.
#ifndef: Opposite of #ifdef. It checks if a macro is not defined. If the macro
is not defined, the code following #ifndef is compiled.
#else: Used with #if, #ifdef, or #ifndef. If the preceding condition is false,
the compiler includes the code following #else.
#elif: Short for “else if”. Allows for multiple conditional expressions. If the
preceding #if, #ifdef, or #ifndef is false, and the condition in #elif is true, the
code following #elif is compiled.
#endif: Marks the end of a conditional compilation block started by #if,
#ifdef, #ifndef, #else, or #elif.
Syntax for #if :
#if condition
// Code to compile if condition is
true
#endif
Explanation:
● #if evaluates the given condition (which is usually a constant
expression).
● If the condition evaluates to true (non-zero), the code block following
#if is included in the program.
● If the condition evaluates to false (zero), the code block is excluded
from the program.
Example :
#include <stdio.h>
#define NUM 10
int main() {
#if NUM > 5
printf("NUM is greater than 5\n");
#endif
return 0;
}
Preprocessed Code (After Preprocessing):
#include <stdio.h>
int main() {
printf("NUM is greater than 5\n");
return 0;
}
Output: NUM is greater than 5
Syntax for #ifndef:
#ifndef MACRO_NAME
// Code to be included if MACRO_NAME is not defined
#endif
Explanation:
● #ifndef stands for "if not defined".
● It checks whether a macro is not defined before including the code
following it.
● If the macro is not defined, the code block after #ifndef will be
included in the program.
● If the macro is defined, the code block will be skipped.
Example :
#include <stdio.h>
#ifndef MY_MACRO
#define MY_MACRO 10
#endif
int main() {
printf("MY_MACRO = %d\n", MY_MACRO);
return 0;
}
Preprocessed Code (After Preprocessing):
#include <stdio.h>
int main() {
printf("MY_MACRO = %d\n", 10);
return 0;
}
Output: MY_MACRO = 10
Note: #ifndef, #define, and #endif are removed before compilation.
Syntax for #ifdef:
#ifdef MACRO_NAME
//Code to be included if MACRO_NAME is defined
#endif
Explanation:
● #ifdef stands for "if defined." It checks whether the macro,
MACRO_NAME has been previously defined using #define.
● If the macro is defined, the code block inside the #ifdef will be
included in the program.
● If the macro is not defined, the code block inside the #ifdef will be
ignored by the preprocessor.
Example:
#include <stdio.h>
#define NUM 10
int main() {
#ifdef NUM
printf("NUM is defined with value: %d\n", NUM);
#endif
return 0;
}
Preprocessed Code (After Preprocessing):
#include <stdio.h>
int main() {
printf("NUM is defined with value: %d\n", 10);
return 0;
}
Output: NUM is defined with value: 10
Syntax of #elif:
Syntax with #else:
#if CONDITION_1
// Code if CONDITION_1 is true
#elif CONDITION_2
// Code if CONDITION_1 is false
and CONDITION_2 is true
#elif CONDITION_3
// Code if CONDITION_1 and
CONDITION_2 are false and
CONDITION_3 is true
#else
// Code block if all conditions are
false
#endif
Key Points:
● #elif is used after an #if or another #elif to provide an
additional condition.
● #elif provides alternative conditions after the initial #if, checking if
specific conditions match. It allows the program to select one of the
code blocks to include based on the value of the defined macro.
● Multiple #elif statements allow checking several conditions in
sequence.
Example:
#include <stdio.h>
#define NUM 10
int main() {
#if NUM > 15
printf("NUM greater than15\n");
#elif NUM == 10
printf("NUM is equal to 10\n");
#else
printf("NUM is less than 10\n");
#endif
return 0;
}
Explanation:
● We define NUM as 10.
● The #if condition checks if NUM is greater than 15. Since it’s not, it
moves to the next condition.
● The #elif condition checks if NUM is equal to 10. This is true, so it
prints "NUM is equal to 10".
● If neither condition was true, the #else would be executed, but in this
case, it is skipped.
Preprocessed Code Output (What remains after preprocessing):
#include <stdio.h>
int main() {
printf("NUM is equal to 10\n");
return 0;
}
Output:
NUM is equal to 10
Syntax of #else
The #else directive is used with #if, #ifdef, #elif or #ifndef to specify an
alternative block of code when the condition specified with any of these is
false.
#if CONDITION
// Code if CONDITION is true
#else
// Code if CONDITION is false
#endif
Example:
#include <stdio.h>
#define VALUE 10
int main() {
#if VALUE > 5
printf("VALUE greater than 5\n");
#else
printf("VALUE is 5 or less\n");
#endif
return 0;
}
Preprocessed Code (After Preprocessing):
#include <stdio.h>
int main() {
printf("VALUE greater than 5\n");
return 0;
}
Output: VALUE greater than 5
Example Using #ifdef
#include <stdio.h>
#define FEATURE_ENABLED
int main() {
#ifdef FEATURE_ENABLED
printf("Feature is enabled!\n");
#else
printf("Feature is disabled!\n");
#endif
return 0;
}
Preprocessed Code (After Preprocessing)
#include <stdio.h>
int main() {
printf("Feature is enabled!\n");
return 0;
}
Output:
Feature is enabled!
If we comment out #define FEATURE_ENABLED, the output will be:
Feature is disabled!
Example Using #ifndef
#include <stdio.h>
#define CONFIG
int main() {
#ifndef CONFIG
printf("CONFIG is not defined!\n");
#else
printf("CONFIG is defined!\n");
#endif
return 0;
}
Preprocessed Code (After Preprocessing)
#include <stdio.h>
#define CONFIG
int main() {
printf("CONFIG is defined!\n");
return 0;
}
Expected Output (when CONFIG is defined):
CONFIG is defined!
Expected Output (when CONFIG is not defined):
CONFIG is not defined!
Example Using #elif and #else
#include <stdio.h>
#define VALUE 20
int main() {
#if VALUE < 10
printf("VALUE is less than 10\n");
#elif VALUE >= 10 && VALUE <= 30
printf("VALUE is between 10 and 30\n");
#else
printf("VALUE is greater than 30\n");
#endif
return 0;
}
Preprocessed Code (After Preprocessing):
#include <stdio.h>
int main() {
printf("VALUE is between 10 and 30\n");
return 0;
}
Expected Output:
VALUE is between 10 and 30
If we change VALUE to 5, the output will be:
VALUE is less than 10
If we set VALUE to 35, the output will be:
VALUE is greater than 30