Objectives
- Concepts and usage of preprocessor directives.
- Concepts and usage of conditional compilation.
Introduction
C provides the facility of pre-processing and modifying a source file, before its actual compilation. Commands and instructions to the C compiler can be incorporated within a C program. A number of preprocessor directives are supported.
The processor directives can be placed anywhere in the program. The general practice is to keep them at the beginning of the program particularly the #include and #define directives.
The preprocessor on encountering a directive performs the modification as directed in that command and thereafter the modified source program is given to the compiler for compilation.
File Inclusion
This is used for incorporating the contents of another file into the C program file. The only directive in this group is:
#include
Macro definition and substitution
This is used for replacement of one string with another. There are two directives in this group:
#define
#undef
Conditional compilation
This is used for compiling selected portions of a program. This group has six directives.
#if
#elif
#else
#endif
#ifdef
#ifndef
Miscellaneous
These are as follows:
#error
#line
#pragma
File Inclusion Directive
With this directive, the contents of the entire specified file replaces the command line at that location in the program. There are a number of files that contain macros for definition of manifest constants, declarations for structures and prototypes for standard library functions.
These are called standard header files and generally have the extension (.h) after the file name. It is not necessary that the header files should have the (.h) extension, it can be (.c) or any valid extension for a C file name. Such files as are needed by a program using library functions can be included by this directive.
This eliminates the need for individual users to define the macros or to write prototype declarations for the functions required. This has the following 3 forms:
#include <stdio.h>
#include ”stdlib.h”
#include “c:\MSC5\spl\test.h”
In the first form, the filename is enclosed between a pair of angle brackets. In this case, the preprocessor searches for the file first in the directories specified as the include directory.
If the file name is enclosed within a pair of double quotes, as in the second form, the preprocessor searches for the file first in the current working directory. User-defined header files or any user created C file can be kept in the current working directory and referred to by including its name within quotes.
The third form of representation will cause the preprocessor to search only in the specified directory.
The #include directives can be nested. In other words, the #include commands can be inside the include files. The levels of nesting are computer dependent.
Macro Definition and Expansion
#define:
This pre-processor directive has a close resemblance to a function. This directive handles what is known as a macro. A macro is a name that will be substituted by a specified text wherever the name appears in the source file. This replacement is done by the pre-processor. The source code with the replaced text is then compiled. The replacement text is known as macro body.
#include ”stdio.h” #define SIZE 100 void main(void) { static char c[SIZE]={“This is an array of characters”}; /*preprocessor will substitute SIZE by 100 before compilation. SIZE is the macro name and 100 is its body*/ Printf(“The string is:\n %s\n”,c); } Output: The string is: This is an array of characters.
In this program, the definition of the macro SIZE appears before the main(). The text 100 replaces the word SIZE wherever it appears inside the program. The compiler will compile the program as though the text 100 was keyed in; it will never be aware of the presence of the word SIZE.
The advantage of such macros is that when the program needs modification for the value of the SIZE and the SIZE appears at a number of locations in the program, it is enough for the values is corrected at one place, which is in the macro definition.
The syntax of this pre-processor command is an under:
#define macro_name macro_body
There is no semicolon at the end of the macro definition. The macro-name should not have any blank embedded inside it. If a blank is present, the portion to the right of the blank will be treated as macro body i.e replacement text. The macro-name is conventionally shown in capital letters although the language does not insist on it.
Macro can have parameters like functions, to which arguments can be passed.
/* use of macros with formal parameters*/ #include “stdio.h” #define SUM(a,b) ((a)+(b)) /* macro SUM with formal parameters a & b defined*/ #define PROMPT “Enter two integers separated by space” /*macro PROMPT defined as a string*/ Void main(void) { int i,j,k; printf(“%s\n”,PROMPT); /*macro PROMPT will be replaced with the given string by the preprocessor before compilation*/ Scanf(“%d%d”,&I,&j); k=SUM(i,j); /*macro SUM will be substituted with the given macro body first: therefore the parameters a and b will be substituted by i and j*/ printf(“value entered are i=%d; j=%d\n”,i,j); printf(“their sum=%d;\n”,k); } Output: Enter two integers separated by space values entered are:i=320;j=270 their sum=590;
Side Effects Using Macros
The use of macros involves literal substitution of the macro_name and its parameters by the macro_body and arguments. When a macro is used in arithmetic expression, unexpected results may be produced due to operator precedence.
/*side effects using macros*/ #include <stdio.h> #define SQUARE(x) x*x #define SQR(X) ((x)*(x)) void main(void) { int i=10, j=2; int wrongsquare, rightsquare; wrongsquare=SQUARE(i+j); rightsquare=SQR(i+j); printf(“i=%d; j=%d; i+j=%d\n”,i,j,i+j); printf(“rightsquare=%d\n”,rightsquare); } Output: i=10; j=2; i+j=12 wrongsquare=32 rightsquare=144
As a safeguard against the side effects of a macro, avoid arguments of the following categories:
- increment and decrement operators
- assignment operator
- other macros
- function calls
Strings In Macros
One of the problems in cases of macros for strings is with regards to the parameter variable appearing embedded in strings delimited by double quotes within a macro body. These variables are not replaced by the pre-processor.
This problem can be solved by ending the quotes before the location of the variable and restarting the quotes after the variable. The variable must be preceded by the character # which is known as string literal creation operator or stringsing operator.
/* use of stringsing operator # in macros*/ #include “sdio.h” #define FORMAT(p) “value of variable “#p” is =%d\n”,p #define FMT(p) “value of variable p is =%d\n”,p Void main(void) { int i=325; printf(FORMAT(i)); printf(FMT(i)); } Output: Value of variables i is=325 Value of variable p is=325
The first line in the output is foe the macro FORMAT and is printed correctly as intended. The variable i should appear in the output and not p. the stringsing operator # takes one operand p here. During preprocessing, p is replaced by the actual variable i. Subsequently, this variable i gets converted into a string, by placing it within a pair of double quotes as “i”. This string then gets concatenated with the adjacent strings already enclosed within pairs of double quotes.
There is also another operator ## which is called string literal concatenation operator or token passing operator. This provides for concatenating actual argument during macro substitution. The parameters are replaced by the corresponding arguments and the ## and surrounding white spaces are removed.
The token passing operator ## takes two operands, one on either side, or when the macro is encountered concatenates the two operands into a single value. The following example illustrates its use:
#define STUDENT(x) student_#x
If the following macro occurs inside a program:
STUDENT(1)
It will get converted to:
student_1
if the macro body is too long to fit in one line, a backslash character \ should be placed at the end of the line and then the remaining portions are entered in the next line.
#undef macro_name
This causes the previous preprocessor directive with this macro_name to lapse. From that location onwards in the program, the previous definition will not hold good. This enables the use of macro_names in limited locations in a program.
Example:
#undef STUDENT(x)
This will cause the STUDENT(x) preprocessor directive with the macro name STUDENT(x) to lapse.
Conditional Compilation:
With the directives available under this group, you can stipulate which portion of a C program should be compiled and under what conditions. It is possible to have a single source file which can be compiled with different compilers and with different machines.
For instance getch() library function is supported by Microsoft C but not by ANSI C. with ANSI C, one has to use the getchar() function instead of the getch() function. If a program has to be compiled either with a Microsoft C compiler or with one fully compatible with ANSI C, the conditional compilation using #if directive can be used as follows.
#define ANSI_C 1 #define MSC5 0 ……..; ……..; #if ANSI_C c=getchar(); putchar (c); #endif #if MCS5 c=getchar(); putch(c); #endif; ………; ………;
Since ANSI_C is defined as 1, the if condition is evaluated to true and all statements up to the next #endif. However, with MSC5 having been defined as 0, the if condition evaluates to a false and the functions getch() and putch() are not included in the program by the preprocessor. We need to only interchange the definitions for ANSI C and MSC5 for inclusion of getch() and putch() in the program.
#if contant_expression statement block; #endif
Multiple options for compilation can be enforced with a series of elif directives following the if, as per the following format:
#if express_1;
statement_1;
#elif expression_2;
statement_2;
#elif expression_3;
statement_3;
#endif;
The elif directive means “else if”
If there are only two options, the #else directive can be used with #if in the following format.
#if expression_1;
statement_1;
#else statement_2;
#endif
#else can also be used with the #elif directive. In all these cases, provision of #endif as the last directive in the group is necessary.
There is a variation to #if directive. This is #ifdef.
The syntax for this is:
#ifdef macro_name
statement;
#endif
Ifdef means “if defined”. Hence, if a particular macro name has been defined in the program, the statements following this directive up to the next #endif will be compiled. There is also another counterpart to this with the following syntax:
#ifndef macro_name statement; #endif
Here, ifndef means “if not defined”. This is negation of #ifdef.
MISCELLANEOUS DIRECTIVES
#line DIRECTIVE
The current line number and the name of the file being processed by the compiler is always maintained by an internal variable. #line directive is used to change these values in a user defined way. The syntax for this is:
#line n “file_name”
Where n is any positive integer, “filename” is optional.
Example:
#line 50 “library”
will change the current line number to 50 and the file name to “library”. These are used for reporting errors with line numbers during compilation.
#pragma DIRECTIVE
This has the following syntax:
#pragma name
This directive allows a compiler designer to define other instructions to the compiler. Therefore, pragmas vary from compiler to compiler. ANSI C does not specify any standard name or instructions for pragmas.
Microsoft C compiler supports 16 pragma names. These directives are used to instruct the compiler to turnoff or on certain features such as organizing the memory layout, inline coding of intrinsic functions, formatting source listings and placing comments in the object file.
#error DIRECTIVE
The syntax of this preprocessor directive is:
#error message
The text of the message is not to be shown in quotes. On the execution of this directive, the C compiler stops compilation and displays the specified message. The line number is also displayed.
Comments/Suggestions are invited. Happy coding......!
Comments Post a Comment