▪ Inclusion of other files in the file being compiled.
▪ Definition of symbolic constants and macros.
▪ Conditional compilation of program code or code segment.
▪ Conditional execution of preprocessor directives.
10.2 The #include Preprocessor Directive
// searches for header files and replaces this directive
// with the entire contents of the header file here
e.g. #include <stdio.h>
A header file containing declarations common to the separate program files is often created and included in the file using this directive. Examples of such common declarations are structure (struct) and union (union) declarations, enumerations (enum), classes, function prototypes, types etc.
We can also use the relative path as used in UNIX system as follows:
This means search for file in the indicated directory, if not found then look in the subdirectory associated with the standard header file.
This means, search for this file in the sys subdirectory under the subdirectory associated with the standard header file.
Remember that from Module 9, '. ' (dot) means current directory and '..' (dot dot) means parent directory.
The #define directive creates symbolic constants, constants that represented as symbols and macros (operations defined as symbols). The format is as follows:
#define identifier replacement-text
When this line appears in a file, all subsequent occurrences of identifier will be replaced by the replacement-text automatically before the program is compiled. For example:
#define PI 3.14159
Replaces all subsequent occurrences of the symbolic constant PI with the numeric constant 3.14159. const type qualifier also can be used to declare numeric constant that will be discussed in another Module.
Symbolic constants enable the programmer to create a name for a constant and use the name throughout the program, the advantage is, it only need to be modified once in the #define directive, and when the program is recompiled, all occurrences of the constant in the program will be modified automatically, making writing the source code easier in big programs.
That means everything, to the right of the symbolic constant name replaces the symbolic constant.
Other #define examples include the stringizing as shown below:
#define STR "This is a simple string"
#define NIL ""
#define GETSTDLIB #include <stdlib.h>
#define HEADER "myheader.h"
A macro is an operation defined in #define preprocessor directive.
As with symbolic constants, the macro-identifier is replaced in the program with the replacement-text before the program is compiled. Macros may be defined with or without arguments.
A macro without arguments is processed like a symbolic constant while a macro with arguments, the arguments are substituted in the replacement text, then the macro is expanded, that is the replacement-text replaces the identifier and argument list in the program.
Consider the following macro definition with one argument for a circle area:
#define CIR_AREA(x) PI*(x)*(x)
Wherever CIR_AREA(x) appears in the file, the value of x is substituted for x in the replacement text, the symbolic constant PI is replaced by its value (defined previously), and the macro is expanded in the program. For example, the following statement:
area = CIR_AREA(4);
Is expanded to:
area = 3.14159*(4)*(4);
Since the expression consists only of constants, at compile time, the value of the expression is evaluated and assigned to variable area.
The parentheses around each x in the replacement text, force a proper order of evaluation when the macro argument is an expression. For example, the following statement:
area = CIR_AREA(y + 2);
Is expanded to:
area = 3.14159*(y + 2)*(y + 2);
This evaluates correctly because the parentheses force the proper order of evaluation. If the parentheses are omitted, the macro expression is:
area = 3.14159*y+2*y+2;
Which evaluates incorrectly (following the operator precedence rules) as:
area = (3.14159 * y) + (2 * y) + 2;
Because of the operator precedence rules, you have to be careful about this.
Macro CIR_AREA could be defined as a function. Let say, name it a circleArea:
double circleArea(double x)
Performs the same calculation as macro CIR_AREA, but here the overhead of a function call is associated with circleArea function.
The advantages of macro CIR_AREA are that macros insert code directly in the program, avoiding function overhead (creating and dismantling the stack), and the program remains readable because the CIR_AREA calculation is defined separately and named meaningfully. The disadvantage is that its argument is evaluated twice. Another better alternative is using the inline function, by adding the inline keyword.
inline double circleArea(double x)
return (3.14159 * x * x);
The following is a macro definition with 2 arguments for the area of a rectangle:
#define RECTANGLE_AREA(p, q) (p)*(q)
Wherever RECTANGLE_AREA(p, q) appears in the program, the values of p and q are substituted in the macro replacement text, and the macro is expanded in place of the macro name. For example, the statement:
rectArea = RECTANGLE_AREA(a+4, b+7);
Will be expanded to:
rectArea = (a+4)*(b+7);
The value of the expression is evaluated and assigned to variable rectArea.
If the replacement text for a macro or symbolic constant is longer than the remainder of the line, a backslash (\) must be placed at the end of the line indicating that the replacement text continues on the next line. For example:
#define RECTANGLE_AREA(p, q) \
Symbolic constants and macros can be discarded by using the #undef preprocessor directive. Directive #undef, undefine a symbolic constant or macro name.
The scope of a symbolic constant or macro is from its definition until it is undefined with #undef, or until the end of the file. Once undefined, a name can be redefined with #define.
Functions in the standard library sometimes are defined as macros based on other library functions. For example, a macro commonly defined in the stdio.h header file is:
#define getchar() getc(stdin)
The macro definition of getchar() uses function getc() to get one character from the standard input stream. putchar() function of the stdio.h header, and the character handling functions of the ctype.h header implemented as macros as well.
A program example.
using namespace std;
#define THREETIMES(x) (x)*(x)*(x)
#define CIRAREA(y) (PI)*(y)*(y)
#define REC(z, a) (z)*(a)
#define PI 3.14159
float p = 2.5;
float r = 3.5, s, t, u = 1.5, v = 2.5;
cout<<"Power to three of "<<p<<" is "<<THREETIMES(p)<<endl;
cout<<"Circle circumference = 2*PI*r = "<<(2*PI*r)<<endl;
s = CIRAREA(r+p);
cout<<"Circle area = PI*r*r = "<<s<<endl;
t = REC(u, v);
cout<<"Rectangle area = u*v = "<<t<<endl;
In another Module we will discuss the inline function that is a better construct used in C++ compared to macros.
Enable the programmer to control the execution of preprocessor directives, and the compilation of program code.
Each of the conditional preprocessor directives evaluates a constant integer expression. Cast expressions, sizeof() expressions, and enumeration constants cannot be evaluated in preprocessor directives.
#define NULL 0
These directives determine whether the NULL is defined or not. The expression defined(NULL) evaluates to 1 if NULL is defined; 0 otherwise. If the result is 0, !defined(NULL) evaluates to 1, and NULL is defined.
Otherwise, the #define directive is skipped. Every #if construct ends with #endif. Directive #ifdef and #ifndef are shorthand for #if defined(name) and #if !defined(name) respectively.
A multiple-part conditional preprocessor construct may be tested using the #elif (the equivalent of else if in an if structure) and the #else (the equivalent of else in an if structure) directives.
During program development, programmers often find it helpful to comment out large portions of code to prevent it from being compiled but if the code contains comments, /* and */ or //, they cannot be used to accomplish this task.
Instead, the programmer can use the following preprocessor construct:
code prevented from compiling...
To enable the code to be compiled, the 0 in the preceding construct is replaced by 1.
Conditional compilation is commonly used as a debugging aid.
Another example shown below, instead using the printf() statements directly to print variable values and to confirm the flow of control, these printf() statements can be enclosed in conditional preprocessor directives so that the statements are only compiled while the debugging process is not completed.
printf("Variable x = %d\n", x);
The code causes a printf() statement to be compiled in the program if the symbolic constant DEBUG has been defined (#defined DEBUG) before directive #ifdef DEBUG.
When debugging is completed, the #define directive is removed from the source file, and the printf() statements inserted for debugging purpose are ignored during compilation. In larger programs, it may be desirable to define several different symbolic constants that control the conditional compilation in separate sections of the source file.
A program example.
#define MyVersion 1.1
using namespace std;
cout<<"Sample using #define, #ifdef, #ifndef\n";
cout<<" #undef, #else and #endif...\n";
cout<<"\nModule10 is defined.\n";
cout<<"\nModule10 is not defined.\n";
cout<<"\nMyVersion is not defined\n";
cout<<"\nMyVersion is "<<MyVersion<<endl;
cout<<"\nMy Revision is defined\n"<<endl;
cout<<"\nMyRevision is not defined!\n"<<endl;
cout<<"MyVersion is not defined\n"<<endl;
cout<<"MyVersion is "<<MyVersion<<endl;
// testing the boolean.h header file from
// multiple inclusion or re-definition
typedef int boolean; // means literal string 'int' is same as 'boolean'
const boolean FALSE = 0;
const boolean TRUE =1;
// program using the user defined header file, boolean.h
using namespace std;
// notice this...
// new type stored in boolean.h...
HappyTime = TRUE;
// if TRUE = 1, do...
cout<<"I'm happy today lor!!!"<<endl;
// else, FALSE = 0, do...
cout<<"What a bad day...today!!!"<<endl;
Let say we want to define a vehicle class in header file named vehicle.h. By using the conditional directives, we can avoid the multiple inclusion of this file when there are multiple #include <vehicle.h> directives in multiple files in the same project.
// The file is compiled only when VEHICLE_H is not defined.
// The first time the file is included using #include <vehicle.h>,
// the macro is not defined, then it is immediately defined.
// Next time, if the same inclusion of the vehicle.h or
// by other source file with the same vehicle.h, needs to be
// included in the same project, the macro and conditional directives
// ensure that the file inclusion is skipped…
// end of the vehicle.h
The usage of the multiple inclusions of similar header files is discussed in Module 14.