15 Beginner's Lesson 9: The C Preprocessor

Lesson Nine attached.

Greetings,

In Lesson Eight we learned a little about variable "scope" and "class",
functions, structured programming techniques, and recursion. We touched
on those things very briefly, so be aware that all of those things can
be studied in detail. When we are made aware of something new, but it
isn't treated in detail, that is an invitation to do some homework on
your own and find out more about it! Lesson Nine will cover the C
Preprocessor: #define statement, conditional compilation, include files,
and parameterized macros. This is a short lesson.

#define statement
-----------------

The general form of a simple define statement is:

#define NAME substitute-text

where NAME can be any valid C identifier and substitute-text can be
anything.

All preprocessor commands start with a hash mark in column one.
A preprocessor directive terminates at end-of-line.
A line may be continued by putting a backslash (\) at the end.
It is common practice to use all uppercase letters for NAME.

The preprocessor does not check for correct C syntax.
The C preprocessor uses an entirely different syntax than the
C compiler. Most beginner errors are due to treating the preprocessor
like the compiler. The preprocessor is nothing more than a specialized
text editor, and it has no understanding of the C programming language.

Conditional Compilation
-----------------------
The preprocessor allows the programmer to change the way code is
generated through the use of conditional compilation. Suppose you
want to put debugging code in the program while you are working on
it, but remove it in the production version. Just include the code
in a #ifdef/#endif section:

#ifdef DEBUG
printf("In compute_hash, value %d hash %d\n", value, hash);
#endif /* DEBUG */


You don't have to put the /* DEBUG */ comment at the end, but it is
very useful.

If the beginning of the program contains the directive:

#define DEBUG /* turn debugging on */

the printf() statement will be included. If the program contains the
directive:

#undef DEBUG /* turn debugging off */

the printf() will be omitted.

Actually, the #undef DEBUG isn't needed. If there isn't a #define DEBUG
statement, then DEBUG is undefined. The #undef DEBUG is used to
explicitly indicate that DEBUG is used for conditional compilation and
is now turned off.

The directive #ifndef will cause the code to be compiled if the symbol
is NOT defined. #else reverses the sense of the conditional:

#ifdef DEBUG
printf("Debugging is ON\n");
#else DEBUG
printf("Production version\n");
#endif DEBUG

#include Files
--------------

The #include directive allows the programmer to use source code from
another file. Files that are included in other programs are called
header files. Angle brackets around the header file indicate that
the file is a standard header file. If you write your own header file,
then it should be included in double quotation marks.

#include <stdio.h> /* Standard header file */
#include "myprog.h" /* programmer-written header file */

Beware of nested include files. Suppose you define several useful
constants in the file "const.h". If the files "data.h" and "io.h"
both include "const.h" and you put this in your program:

#include "data.h"
#include "io.h"

you will probably generate errors because the preprocessor will set
the definitions in "const.h" twice. Defining a constant twice is not
a fatal error, however, defining a data structure or union twice is a
fatal error, and must be avoided.

One way to avoid this problem is to have "const.h" check to see if it
has already been included and does not define any symbol that has already
been defined. The #ifndef SYMBOL is true if the symbol is NOT defined.

#ifndef _CONST_H_INCLUDED_
/* define constants here */
#define _CONST_H_INCLUDED_
#endif _CONST_H_INCLUDED_

When "const.h" is included, it defines the symbol _CONST_H_INCLUDED_.
If that symbol is already defined (because "const.h" was already
included earlier), the #ifndef conditional hides all the constant
defines.

Parameterized Macros
--------------------
So far, just simple macros have been discussed: #define FOO bar
Macros can take parameters. The following macro will compute the
square of a number:

#define SQR(x) ((x) * (x)) /* square a number */

When used, the macro will replace x by the text:

SQR(5) expands to ((5) * (5))

It is a good rule to put parentheses around the parameters of a macro.

Here are some exercises to work on:

[1] Write a macro that returns TRUE if its parameter is divisble by 10
and FALSE otherwise.

[2] Write a macro is_decnum that returns TRUE if its argument is a
decimal number.

[3] Write a second macro is_hexnum that returns TRUE if its argument
is a hexadecimal digit (0-9, A-F, a-f). This macro should reference
the first macro: is_decnum.

[4] Write a preprocessor macro that swaps two integers. (For the real
hacker write one that does not use a temporary variable declared
outside the macro.)

Happy Programming!
--
K