Header Files and Macros
Header Files and Macros
The C Preprocessor
The C preprocessor modifies a source code file before handing it over to the compiler. You're most
likely used to using the preprocessor to include files directly into other files, or #define constants, but
the preprocessor can also be used to create "inlined" code using macros expanded at compile time and
to prevent code from being compiled twice.
There are essentially three uses of the preprocessordirectives, constants, and macros. Directives are
commands that tell the preprocessor to skip part of a file, include another file, or define a constant or
macro. Directives always begin with a sharp sign (#) and for readability should be placed flush to the
left of the page. All other uses of the preprocessor involve processing #define'd constants or macros.
Typically, constants and macros are written in ALL CAPS to indicate they are special (as we will see).
Header Files
The #include directive tells the preprocessor to grab the text of a file and place it directly into the
current file. Typically, such statements are placed at the top of a programhence the name "header file"
for files thus included.
Constants
If we write
#define [identifier name] [value]
If you are defining a constant in terms of a mathematical expression, it is wise to surround the entire
value in parentheses:
#define PI_PLUS_ONE (3.14 + 1)
By doing so, you avoid the possibility that an order of operations issue will destroy the meaning of your
constant:
x = PI_PLUS_ONE * 5;
which would result in 1 * 5 being evaluated before the addition, not after. Oops!
Conditional Compilation
There are a whole set of options that can be used to determine whether the preprocessor will remove
lines of code before handing the file to the compiler. They include #if, #elif, #else, #ifdef, and #ifndef.
An #if or #if/#elif/#else block or a #ifdef or #ifndef block must be terminated with a closing #endif.
The #if directive takes a numerical argument that evaluates to true if it's nonzero. If its argument is
false, then code until the closing #else, #elif, of #endif will be excluded.
Conditional compilation is a particularly useful way to comment out a block of code that contains
multiline comments (which cannot be nested).
#if 0
/* comment ...
*/
// code
/* comment */
#endif
Another common problem is that a header file is required in multiple other header files that are later
included into a source code file, with the result often being that variables, structs, classes or functions
appear to be defined multiple times (once for each time the header file is included). This can result in a
lot of compiletime headaches. Fortunately, the preprocessor provides an easy technique for ensuring
that any given file is included once and only once.
By using the #ifndef directive, you can include a block of text only if a particular expression is
undefined; then, within the header file, you can define the expression. This ensures that the code in the
#ifndef is included only the first time the file is loaded.
#ifndef _FILE_NAME_H_
#define _FILE_NAME_H_
/* code */
#endif // #ifndef _FILE_NAME_H_
Notice that it's not necessary to actually give a value to the expression _FILE_NAME_H_. It's
sufficient to include the line "#define _FILE_NAME_H_" to make it "defined". (Note that there is an n
in #ifndefit stands for "if not defined").
A similar tactic can be used for defining specific constants, such as NULL:
#ifndef NULL
#define NULL (void *)0
#endif // #ifndef NULL
Notice that it's useful to comment which conditional statement a particular #endif terminates. This is
particularly true because preprocessor directives are rarely indented, so it can be hard to follow the flow
of execution.
#error "Aargh!"
This prints "Gaah!" in the compiler output and halts the computation at that point. This is extremely
useful for determining whether a given line is being compiled or not. It is also useful if you have a
heavily parameterized body of code and want to make sure a particular #define has been introduced
from the makefile, e.g.:
#ifdef WINDOWS
... /* Windows specific code */
#elif defined(UNIX)
... /* Unix specific code */
#else
#error "What's your operating system?"
#endif
Some implementations provide a nonstandard #warning directive to print out a warning message in the
compiler output, but not stop the compilation process. A typical use is to warn about the usage of some
old code, which is now unfavored and only included for compatibility reasons, e.g.:
#warning "Do not use ABC, which is deprecated. Use XYZ instead."
Although the text following the #error or #warning directive does not have to be quoted, it is good
practice to do so. Otherwise, there may be problems with apostrophes and other characters that the
preprocessor tries to interpret.
Macros
The other major use of the preprocessor is to define macros. The advantage of a macro is that it can be
typeneutral (this can also be a disadvantage, of course), and it's inlined directly into the code, so there
isn't any function call overhead. (Note that in C++, it's possible to get around both of these issues with
templated functions and the inline keyword.)
They look a lot like function calls, but they're not so simple. There are actually a couple of tricky points
when it comes to working with macros. First, remember that the exact text of the macro argument is
"pasted in" to the macro. For instance, if you wrote something like this:
#define MULT(x, y) x * y
what value do you expect z to end up with? The obvious answer, 30, is wrong! That's because what
happens when the macro MULT expands is that it looks like this:
So z would end up with the value 13! This is almost certainly not what you want to happen. The way to
avoid it is to force the arguments themselves to be evaluated before the rest of the macro body. You can
do this by surrounding them by parentheses in the macro definition:
But this isn't the only problem. It is also generally a good idea to surround the macro's code in
parentheses if you expect it to return a value. Otherwise, you can get similar problems as when you
define a constant. For instance, the following macro, which adds 5 to a given argument, has problems
when embedded within a larger statement:
int x = ADD_FIVE(3) * 3;
// this expands to (3) + 5 * 3, so 5 * 3 is evaluated first
// Now x is 18, not 24!
To fix this, you generally want to surround the whole macro body with parentheses to prevent the
surrounding context from affecting the macro body.
int x = ADD_FIVE(3) * 3;
On the other hand, if you have a multiline macro that you are using for its side effects, rather than to
compute a value, you probably want to wrap it within curly braces so you don't have problems when
using it following an if statement.
int x = 10;
int y = 5;
// works OK
SWAP(x, y);
When SWAP is expanded in the second example, only the first statement, a ^= b, is governed by the
conditional; the other two statements will always execute. What we really meant was that all of the
statements should be grouped together, which we can enforce using curly braces:
By the way, note that we didn't surround the arguments in parentheses because we don't expect anyone
to pass an expression into swap!
More Issues
By now, you've probably realized why people don't really like using macros. They're dangerous, they're
picky, and they're just not that safe. Perhaps the most irritating problem with macros is that you don't
want to pass arguments with "side effects" to macros. By side effects, I mean any expression that does
something besides evaluate to a value. For instance, ++x evaluates to x+1, but it also increments x. This
increment operation is a side effect.
The problem with side effects is that macros don't evaluate their arguments; they just paste them into
the macro text when performing the substitution. So something like
#define MAX(a, b) ((a) < (b) ? (b) : (a))
int x = 5, y = 10;
int z = MAX(x++, y++);
will end up looking like this:
int x = (x++ < y++ ? y++ : x++)
The problem here is that y++ ends up being evaluated twice! The nasty consequence is that after this
expression, y will have a value of 12 rather than the expected 11. This can be a real pain to debug!
Multiline macros
Until now, we've seen only short, one line macros (possibly taking advantage of the semicolon to put
multiple statements on one line.) It turns out that by using a the "\" to indicate a line continuation, we
can write our macros across multiple lines to make them a bit more readable.
#define SWAP(a, b) { \
a ^= b; \
b ^= a; \
a ^= b; \
}
Notice that you do not need a slash at the end of the last line! The slash tells the preprocessor that
the macro continues to the next line, not that the line is a continuation from a previous line.
Aside from readability, writing multiline macros may make it more obvious that you need to use curly
braces to surround the body because it's more clear that multiple effects are happening at once.
Programming Assignment
1. You need to include the lab4.h in both the code files to be created for today's lab.
2. Create a file called preprocessor.c which shall have the following components:
1. A macro called SQUARE which squares the input value.
2. A defined constant called PI whose value is set to 3.14.
3. two functions called
1. double circle_area_old (double)
This function should find the area of a circle without using the macro or the defined
value. This function should have a warning inside which warns the user that the method
is deprecated.
2. double circle_area_new(double)
This function finds the area of a circle using the macro and the defined label PI.
3. Create a file called lab4.c
1. This file should contain the main function which invokes the two functions of the
preprocessor.c file.
2. This file should use the ifdef directive to make sure that the functions are invoked only when
the proper header file has been included, else it should print a message saying that the
relevant files have not been included. This can be accomplished by defining a value in the
header file and including the header file and calling its functions only when the value is
defined.