Preprocessor Directives

Oracle Certification Program Candidate Guide


As mentioned in the fourth chapter, a preprocessor directive, is by itself not an executable statement but is an instruction to the C compiler to perform certain task during the compilation phase. We have already seen the usage of #define and #include directives for declaring symbolic constants and for including necessary files. The #define directive replaces the symbolic constants defined by equivalent texts at the initial stage of the compilation process, that allows the programmer to simplify program organisation by implementing shorthand notations.

14.1 MACROS

Apart from the above mentioned facility, the #define statement can be used to introduce macros in C programs. Macros are identifiers that are substituted by expressions, complete statements or group of statements (to an extent it resembles to a function call but differs in the way they are defined). They form a part of preprocessor directives, and are treated differently during the compilation process.

# define macro-name replacement text

The replacement text may continue several lines, in that case a backslash (\) is typed at the end of line to maintain continuity.

A macro definition may include arguments, so the code (replacement text) changes according to the manner it is called. Consider the following example,

#define div(x,y) ((x) == 0? 0: (x) / (y))

Unlike a conventional function call, the above macro div behaves more like an in-line code. Here, each occurrence of a formal parameter is replaced by the corresponding actual parameter, where the actual parameter can belong to any data type not like a conventional function call that expects the data types of the formal and actual argument should match.

Therefore, the line of code,

res=div(a+b,a-b);

is substituted by the macro expansion,

res=((a+b) == 0? 0 : (a+b) / (a-b));

Enough care should be taken to avoid situation like the following,

res=div(i++,j++);

where the increment operator used in the expression substitutes the actual parameter and eventually the expansion of the above macro leads to the evaluation of an expression.

14.2 THE # AND ## OPERATOR

The operator # (stringizing) converts a formal parameter to a string during macro expansion. If used as a prefix of a formal argument, the # operator converts the actual parameter into string by enclosing it by double-quotes. The conversion of the formal parameter to a string involves the following activities :

a) The multiple occurrences of whitespace characters are squeezed to one space.

b) Special character such as ‘ , ” and ) are replaced by the corresponding escape sequence.

c) Finally the string is combined with any adjacent strings.

Example 14.1 :

#define warning(message) printf(#message “\n”)

/* inside the function body the macro is called as */

warning(Do your assignments.);

The preprocessor operator ## (token posting operator) combines actual parameters in a macro. For example, the following macro getitem concatenates its two arguments ,

#define getitem(item,no) item ## no

where the statement in the program function,

getitem(TV,PHX14);

creates a token TVPHX14.

14.3 THE #undef DIRECTIVE

The #undef directive helps in redefining a macro in a C program. For example, the programmer may wish to redefine the value of a symbolic constant NULL specified in header file stdio.h. A simpler way to achieve this is to include the complete header file (stdio.h) and undefine the constant first and finally redefine that using a new constant value (#define). Consider the following preprocessor directives :

Example 14.2 :

#include

#undef NULL

#define NULL -1 /* redefines the NULL value to -1 */

In practice, it would be dangerous to redefine the value of NULL but similar directives may be used to redefine user defined symbolic constants.

14.4 CONDITIONAL COMPILATION- #ifdef, #ifndef, #else, and #endif

The preprocessor provides directives for selectively removing sections of the code, or deciding between two possible sections during the compilation process. Such directives ensure conditional compilation of a C source code.

For example, consider an application developed in C language that uses two different blocks of codes for storing or manipulating data either by using the data files or by using the linked lists. The list implementation requires uninterrupted power supply since they are maintained in the RAM. Because of this reason some users may prefer the file implementation of their application. Thus depending on client’s request either one of the two blocks of codes needs to be compiled. Consider the following example :

Example 14.3 :

# define LL /* symbolic constant for linked list implementation */

# define OF /* symbolic constant for file implementation */

#undef LL

#ifdef LL

create_list(start,data);

#endif

#ifdef OF

store_rec(file_ptr,data);

#endif

The #ifdef and #endif is quite similar to the if conditional constructs of C. In the above example, since the symbolic constant LL is undefined, the immediate #ifdef returns false and the create­_list is ignored during compilation. The #undef OF can be replaced #undef LL to reinstate the linked list implementation of the application.

It is quite clear from the above example that only one of the two blocks is taken into consideration during compilation. Thus if it is not a linked list, it has to be the file operation. The preprocessor directive #else can be used to simplify the above mentioned directives, as shown below :

#ifdef LL

create_list(start,data);

#else

store_rec(file_ptr,data);

#endif

The preprocessor directive #ifndef is a conditional test that returns true if the macro is undefined.

Example 14.4 :

#ifndef TRUE

#define TRUE 1

#endif

The #if Directive

The #ifdef and #ifndef directives test whether a symbol is defined, but it is not possible to test whether the symbol has a specific value. Apart from this, the logical operators AND (&&), OR (||) do not feature in the test condition, thus restricting the test condition to be determined by the value of a single symbolic construct. These two drawbacks can be overcome by using the more general #if directive (quite similar to if conditional construct).

Let us reconsider the preceding example used for explaining #ifdef and #ifndef. Here we substitute them with #if directive using a symbolic constant USER_CHOICE which has value 0 to indicate file operations, and 1 for linked list implementation. Any other value for USER_CHOICE indicates array implementation for the same application. Since the code segments are excluded during compilation, depending on USER_CHOICE, the object code thus generated is optimised according to user need. This feature allows the programmer to trim the complete code to fit user’s requirement avoiding direct removal of code segment which may lead to errors.

Example 14.5 :

#define USER_CHOICE 0 /* USER_CHOICE may contain 0,1 or any number */

#if USER_CHOICE == 0

store_rec(file_ptr,data);

#else

#if USER_CHOICE == 1

create_list(start, data);

#else

insert_array(arr,data);

#endif

#endif

The logical operators like &&,|| can be used to combine two or more symbols in the test condition, for example,

#if (USER_CHOICE != 0 && USER_CHOICE != 1)

create_array(arr,data);

#endif

1. What is the purpose of each of the following groups of preprocessor directives? Explain.

a) # if !defined (FULL)

#define FULL

#endif

b) # if defined (PASCAL)

# define BEGIN {

# define END }

#endif

c) #ifdef FAHRENHEIT

# define temp(t) 1.8 * t + 32

# else

#define temp(t) (t - 32) * 5/9

#endif

d) #ifndef ERROR

#define result printf (“The value of a = %f\n”, a)

# elif FLAG = = 1

#define result printf(“Array index j = %d of the array b = %f\n”, j,*(b + j))

#else

#define result for (i = 1; i <= n; ++i)

printf(“Array index i = %d of array b = %f\n”, i,b[i])

#else

#define result for (*k = 1; *k <= n; ++*k)

printf (“Array index k = %d of array b = %f\ n”,*k,*(b+*k))

#endif

e) # if defined (FLAG)

#undef FLAG

#endif

f) # ifdef DIVISION_ERROR

#define prompt(text) printf(#text)

#endif

g) #if defined (DIVISION_ERROR)

#define prompt(s) printf (“%s\n”, mesg ## s)

#endif

0 comments: