
    
    
                                                        Chapter 6
                                               THE C PREPROCESSOR

AIDS TO CLEAR PROGRAMMING
-----------------------------------------------------------------
The preprocessor is a program that is executed just prior to the 
execution of the compiler.  It's operation is transparent to you 
but it does a very important job.  It removes all comments from 
the source and performs a lot of textual substitution based on 
your code, passing the result to the compiler for the actual 
compilation of your code.

Load and display the file named DEFINE.C for     ================
your first look at some defines and macros.          DEFINE.C
Notice lines 4 through 7 of the program, each    ================
starting with #define.  This is the way all 
defines and macros are defined.  Before the actual compilation 
starts, the compiler goes through a preprocessor pass to resolve 
all of the defines.  In the present case, it will find every 
place in the program where the combination START is found and it 
will replace it with the 0 since that is the definition.  The 
compiler itself will never see the word START, so as far as the 
compiler is concerned, the zeros were always there.  Note that if 
the string is found in a string constant or in a comment, it will 
not be changed.

It should be clear to you that putting the word START in your 
program instead of the numeral 0 is only a convenience to you and 
actually acts like a comment since the word START helps you to 
understand what the zero is used for.

In the case of a very small program, such as that before you, it 
doesn't really matter what you use.  If, however, you had a 2000 
line program before you with 27 references to START, it would be 
a completely different matter.  If you wanted to change all of 
the STARTs in the program to a new number, it would be simple to 
change the one #define statement to the new value.  If this 
technique were not used, it would be difficult to find and 
change all of the references to it manually, and possibly 
disastrous if you missed one or two of the references.

In the same manner, the preprocessor will find all occurrences of 
the word ENDING and change them to 9, then the compiler will 
operate on the changed file with no knowledge that ENDING ever 
existed.

It is a fairly common practice in C programming to use all 
capital letters for a symbolic constant such as START and ENDING 
and use all lower case letters for variable names.  You can use 
any method you choose since it is mostly a matter of personal 
taste.


                                                         Page 6-1

                                   Chapter 6 - The C Preprocessor
     
IS THIS REALLY USEFUL?
-----------------------------------------------------------------
When we get to the chapters discussing input and output, we will 
need an indicator to tell us when we reach the end-of-file of an 
input file.  Since different compilers use different numerical 
values for this, although most use a minus 1, we will write the 
program with a #define to define the EOF used by our particular 
compiler.  If, at some later date, we change to a new compiler, 
it is a simple matter to change this one #define to fix the 
entire program.  In essentially all C compilers, the EOF is 
defined in the STDIO.H file.  You can observe this for yourself 
by examining the contents of the STDIO.H file that was supplied 
with your compiler.


WHAT IS A MACRO?
-----------------------------------------------------------------
A macro is nothing more than another define, but since it is 
capable of at least appearing to perform some logical decisions 
or some math functions, it has a unique name.  Consider line 6 of 
the program on your screen for an example of a macro.  In this 
case, anytime the preprocessor finds the word MAX followed by a 
group in parentheses, it expects to find two terms in the 
parentheses and will do a replacement of the terms into the 
second part of the definition.  Thus the first term will replace 
every A in the second part of the definition and the second term 
will replace every B in the second part of the definition.  When 
line 15 of the program is reached, index will be substituted for 
every A, and count will be substituted for every B.  Once again, 
it must be stated that string constants and comments will not be 
affected.  Remembering the cryptic construct we studied a couple 
of chapters ago will reveal that mx will receive the maximum 
value of index or count.  In like manner, the MIN macro will 
result in mn receiving the minimum value of index or count.  
These two particular macros are very common in C programs.

When defining a macro, it is imperative that there is no space 
between the macro name and the opening parenthesis.  If there is 
a space, the compiler cannot determine that it is a macro, but 
will handle it like a simple substitution define statement.

The results of the macro usage are then printed out in line 17.  
There are a lot of seemingly extra parentheses in the macro 
definition but they are not extra, they are essential.  We will 
discuss the extra parentheses in our next example program.  Be 
sure to compile and execute DEFINE.C before going on to the next 
example program.







                                                         Page 6-2

                                   Chapter 6 - The C Preprocessor

LET'S LOOK AT A WRONG MACRO
-----------------------------------------------------------------
Load the file named MACRO.C and display it on   =================
your screen for a better look at a macro and         MACRO.C
its use.  Line 4 defines a macro named WRONG    =================
that appears to evaluate the cube of A, and 
indeed it does in some cases, but it fails miserably in others.  
The second macro named CUBE actually does get the cube in all 
cases.

Consider the program itself where the CUBE of i+offset is 
calculated.  If i is 1, which it is the first time through, then 
we will be looking for the cube of 1+5 = 6, which will result in 
216. When using CUBE, we group the values like this, 
(1+5)*(1+5)*(1+5) = 6*6*6 = 216.  However, when we use WRONG, we 
group them as 1+5*1+5*1+5 = 1+5+5+5 = 16 which is a wrong answer.  
The parentheses are therefore required to properly group the 
variables together.  It should be clear to you that either CUBE 
or WRONG would arrive at a correct answer for a single term 
replacement such as we did in the last program.  The correct 
values of the cube and the square of the numbers are printed out 
as well as the wrong values for your inspection. 

In line 7 we define the macro ADD_WRONG according to the above 
rules but we still have a problem when we try to use the macro in 
lines 25 and 26.  In line 26 when we say we want the program to 
calculate 5*ADD_WRONG(i) with i = 1, we get the result 5*1 + 1 
which evaluates to 5 + 1 or 6, and this is most assuredly not 
what we had in mind.  We really wanted the result to be 5*(1 + 1) 
= 5*2 = 10 which is the answer we get when we use the macro named 
ADD_RIGHT, because of the extra parentheses around the entire 
expression in the definition given in line 8.  A little time 
spent studying the program and the result will be worth your 
effort in understanding how to use macros.

In order to prevent the above problems, most experienced C 
programmers include parentheses around each variable in a macro 
and additional parentheses around the entire expression.  This 
will allow any macro to work correctly.

The remainder of the program is simple and will be left to your 
inspection and understanding.


CONDITIONAL COMPILATION - PART 1
-----------------------------------------------------------------
The example program named IFDEF.C is our first    ===============
illustration of a conditional compilation.            IFDEF.C
OPTION_1 is defined in line 4, and is             ===============
considered defined for the entire program.  
Therefore when the preprocessor gets to line 6, it keeps the text 
between lines 6 and 8 in the program and passes it to the 
compiler.  If OPTION_1 was not defined when we reach line 6, the 

                                                         Page 6-3

                                   Chapter 6 - The C Preprocessor

preprocessor would throw away line 7 and the compiler would never 
see it.  Likewise line 17 is conditionally compiled based on 
whether OPTION_1 is defined or not.  This is a very useful 
construct, but not the way we are using it here.  Generally it is 
used to include a feature if we are using a certain processor, a 
certain operating system, or even a special piece of hardware.

You should compile and execute the program as is, then comment 
out line 4 so that OPTION_1 will not be defined, and recompile 
and execute the program.  You will see that the extra line will 
not be printed because it will be thrown away by the 
preprocessor.  Keep in mind that the preprocessor does only 
textual substitution or text removal and you will be able to use 
it effectively.

Line 23 illustrates an undefine command to the preprocessor.  
This removes the fact that OPTION_1 was defined and from this 
point on, the program acts as though it were never defined.  Of 
course, it does no good here since the program is completed and 
there are no executable statements following the undefine, but it 
does illustrate the undefine statement.

You should move the undefine to line 5, recompile and execute the 
program, and you will see that it acts as though OPTION_1 was 
never defined.


CONDITIONAL COMPILATION - PART 2
-----------------------------------------------------------------
The next example program illustrates the         ================
preprocessor directive which includes code if        IFNDEF.C
a symbol in not defined.  The ifndef directive   ================
reads literally, "if not defined", and with 
that much definition, its operation should be intuitive.  This 
program will be a real exercise in logic for the diligent student, 
but should be understandable with a little effort.  The symbol 
OPTION_1 is reversed from the last program and the symbol 
PRINT_DATA is used to enable printing if it is not defined.  If 
it is not defined, there will be some printout.  This example 
program, much like the last one, is rather silly but illustrates 
the use of preprocessor directives.  The next program is a little 
more practical.


CONDITIONAL COMPILATION - PART 3
-----------------------------------------------------------------
The program named DEBUGEX.C is a good           =================
illustration of a very practical use of the         DEBUGEX.C
preprocessor.  In this program we define a      =================
symbol named MY_DEBUG at the beginning of the 
program.  When we reach the code in the main program we see why 
it is defined.  Apparently we do not have enough information to 
complete this code, so we sort of slopped it in until we have a 

                                                         Page 6-4

                                   Chapter 6 - The C Preprocessor

chance to talk to Bill and Linda about how to do these 
calculations.  In the meantime, we wish to continue work on other 
parts of the program, so we use the preprocessor to temporarily 
throw away this uncompilable code for us.  Because of the 
obnoxious message we put into line 14, it will be impossible for 
us to forget about the bad state of affairs we left the code in, 
so we are forced to come back later and clean it up.

In this case, we are only concerned with a few lines of code, but 
it could be a large block of code we are working with.  We could 
also be using this technique to handle several large blocks of 
code, some of which are in other modules, until Bill returns to 
explain the analysis and we can complete the undefined blocks.


MULTIPLE FILE PROGRAMS
-----------------------------------------------------------------
For very small programs, it is expedient to include all of the 
code in a single file, and compile that one file for the final 
resulting code.  It is not generally acceptable to do this 
because all but the most trivial programs are too big to place 
in a single file because the file gets to be very cumbersome to 
work with.  It is not at all unusual for a C program to be made 
up of over a thousand source files.  It is, of course, necessary 
for these files to communicate and work together as one large 
program.

Even though it is best not to use global variables, a variable 
that is defined outside of any function, it is sometimes 
expedient to use a few.  Sometimes these variables need to be 
referenced by two or more different files, and C provides a way 
to do this.  Consider the following three file portions.
    
  FILE1.C               FILE2.C               FILE3.C
int index;            extern int index;     extern int index;
extern int count;     int count;
                      static int value;     int value;
                      int main();
static void one();    void two();           void three();

The variable named index declared in FILE1.C is available to any 
other file for use because it is declared globally.  The other 
two files make use of the same variable by declaring it as an 
extern variable.  In essence, they are telling the compiler, "I 
wish to use the variable named index which is defined somewhere 
else".  Anytime index is referred to in either of the other two 
files, the variable of that name is used from FILE1.C, and it can 
be read, or modified by any of the three files.  This provides an 
easy way to pass data from any file to any other file, but it 
could lead to problems.  It would be very easy for any of these 
files to modify index in some way not meant to and corrupt the 
data.  It could be very difficult to determine which file 
corrupted the value of index.

                                                         Page 6-5

                                   Chapter 6 - The C Preprocessor


The variable named count is defined in FILE2.C and referred to in 
the same manner defined above within FILE1.C, but is not 
available for use in FILE3.C because it is not declared in it.  A 
static variable, such as value in FILE2.C cannot be referenced in 
any other file but is hidden in the declaring file by definition.  
A completely separate variable named value is declared in FILE3.C 
that has nothing to do with the same named variable in FILE2.C.  
In this case, FILE1.C could declare value as an external variable 
and refer to that variable in FILE3.C if desired.

The main() entry point can only be called by the operating system 
to get the program started, but the functions two() and three() 
can be called from anywhere within the three files because they 
are global functions.  The function one() however, because it is 
declared static, can only be called from within the file in which 
it is declared.  It cannot be called from within FILE2.C or 
FILE3.C.  It is sometimes expedient to "hide" a function within a 
file, and it is often referred to as a local function as opposed 
to being a global function.


WHAT IS AN ENUMERATION VARIABLE?
-----------------------------------------------------------------
Load and display the program named ENUM.C for    ================
an example of how to use the enum type                ENUM.C
variable.  Line 6 contains the first enum type   ================
variable named result which is a variable that 
can take on any of the values contained within the braces.  
Actually the variable result is an int type variable but can be 
assigned any of the values defined for an int type variable.  The 
names within the parentheses are int type constants and can be 
used anywhere it is legal to use an int type constant.  The 
constant WIN is assigned the value of 0, TIE the value 1, BYE the 
value 2, etc.

In use, the variable named result is used just like any int 
variable would be used as can be seen by its use in the program.  
The enum type of variable is intended to be used by you, the 
programmer, as a coding aid since you can use a constant named 
MON for control structures rather than the meaningless (at least 
to you) value of 1.  Notice that days is assigned the values of 
days of the week in the remainder of the program.  If you were to 
use a switch statement, it would be much more meaningful to use 
the labels SUN, MON, etc, rather than the more awkward 0, 1, 2, 
etc.

All caps are used for the enumeration values in this program as a 
matter of personal taste because they are all constants.  There 
is no universal standard on this matter and each programmer is 
free to do as he wishes.  All caps for these values tends to be 
standard practice however. 


                                                         Page 6-6

                                   Chapter 6 - The C Preprocessor

WHAT IS A PRAGMA?
-----------------------------------------------------------------
A pragma is an instruction to your compiler to perform some 
particular action at compile time.  Although pragmas vary from 
compiler to compiler and are not standardized, they perform some 
useful functions.  Your compiler probably supports some way for 
you to select the optimization method by inserting a pragma into 
the source code.  If your compiler provides a source listing 
file, you probably have pragmas to format the output listing to 
your personal preference.  Check your documentation for the 
pragmas that are provided by your compiler.


PROGRAMMING EXERCISE
-----------------------------------------------------------------
1.  Write a program to count from 7 to -5 by counting down. Use 
    #define statements to define the limits. (Hint, you will need 
    to use a decrementing variable in the third part of the for 
    loop control.



































                                                         Page 6-7
