Physics and Astronomy |
Back to top
On this page
Contents Multiple files and the pre-processorSplitting source code between filesAs programs get larger it makes sense to split the code between different files, with related functions all going into the same file. These are referred to as source files and have the file-name suffix ".c".
Header files: "mydefs.h"
|
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#ifdef M_PI | ||||
// Using system value of pi | ||||
#define PI M_PI | ||||
#else | ||||
// Using our value of pi | // Using our value of pi | |||
#define PI 3.14159265358979 | ||||
#endif | ||||
int main() { | int main() { | |||
double twopi = 2 * PI; | double twopi = 2 * 3.14159265358979; | |||
return 0; | return 0; | |||
} | } |
NB: for clarity we have used an option to the C preprocessor that preserves comments, when compiling they are replaced by spaces.
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#ifdef M_PI | ||||
// Using system value of pi | // Using system value of pi | |||
#define PI M_PI | ||||
#else | ||||
// Using our value of pi | ||||
#define PI 3.14159265358979 | ||||
#endif | ||||
int main() { | int main() { | |||
double twopi = 2 * PI; | double twopi = 2 * 3.14159265358979323846; | |||
return 0; | return 0; | |||
} | } |
C also defines #ifndef M_PI for "if M_PI has not been #defined".
Using the the preprocessor to handle differences between operating systems or compilers, is very common. Note that #ifdef doesn't care what value M_PI has been defined with, just that it has been defined.
#if #elseif #else #endif cause the preprocessor to exclude parts of the source file
#if and #ifdef operate before the code ever gets to the compiler, and the "false" sections are completely removed. Consider the following rather extreme example:
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#define ABC "Alpha Bravo Charlie\n" | ||||
#ifndef ABC | ||||
Chim chiminey, chim chiminey, | ||||
chim chim cher-oo. | ||||
My dad is a dalek and I'm Dr Who! | ||||
#else | ||||
int main() { | int main() { | |||
printf(ABC); | printf("Alpha Bravo Charlie\n"); | |||
return 0; | return 0; | |||
} | } | |||
#endif |
Strange as it looks this is perfectly legal C as the "Chim chimeny.." text is removed before the code is compiled.
Before we go on to some examples of using #ifdef, a couple of points are worth mentioning.
#undef FOO
removes any definition of FOO. It does not matter if FOO had not previously been defined.
We've already met one use of an empty #define, with assert.h:
#define NDEBUG #include <assert.h>
which turns off the abort() macro. In this case any subsequent occurance of NDEBUG in the code would simply be removed.
"Empty" #defines like this are often used when their job is simply to turn on or off sections of code later on the the file(s) They have another use when defining debugging macros as we shall see below.
Chopping out whole sections of the program before they even get to the compiler is a pretty extreme measure and it tends to get used in a few specific circumstances:
We don't normally want to include a header file twice by mistake. We can guard against this by using the preprocessor. For example our previous header file alloc.h might actually look like this:
#ifndef _ALLOC_H #define _ALLOC_H void *xmalloc(size_t n); void *xrealloc(void *old, size_t n); #endif
Should this accidentally be #included twice into a source file then the second time around the preprocessor will strip it out. This is an almost-universal convention, for example on my system the file math.h begins with a comment followed by:
#ifndef _MATH_H #define _MATH_H 1
Protect header files with #ifndef _FILENAME_H
When developing code it's quite common to want to have debugging statements but to turn them off when the code goes out to other people. One way to do this is to have a line at the top of our include file like this:
This can be removed when the code goes "live". Later on in the include file, and sometimes in the .c files, there will be sections of the form:
#ifdef DEBUG ... debugging code #else ... normal code #endif
This allows us to define an optional "I'm here" macro to help us with cases where we are not quite sure the order in which functions are being called, how many times loops are running, etc:
#ifdef DEBUG #define IAMHERE fprintf(stderr, "%s() line %d\n", __func__, __LINE__) #else #define IAMHERE #endif
The second half of this looks strange but without it, if a function contained the statement:
IMHERE;
and DEBUG had not been defined then the compiler would not know what IMHERE meant an the compilation would fail. So we put in an empty #define which is replaced by an empty string.
As it stands we have to declare global variables twice: first as extern in the include file, and then once without the extern in a .c file. But what if we are too laz busy to do this twice?
One of my header files looks like this (with several hundred lines missed out):
#ifndef GLOBALS_H #define GLOBALS_H #ifndef EXTERN #define EXTERN extern #endif EXTERN int mouseaction, popup, msize; #endif
There are many other lines starting with EXTERN. Every file except one (main.c) has #include "globals.h" as its very first line:
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#include "globals.h" | extern int mouseaction, popup, msize; | |||
void somefunction(void) { | void somefunction(void) { | |||
// More code here.. | // More code here.. | |||
} | } |
But main.c looks like this:
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#define EXTERN | int mouseaction, popup, msize; | |||
#include "globals.h" | ||||
int main() { | int main() { | |||
// ... | // ... | |||
return 0; | return 0; | |||
} | } |
(note the missing "extern") and we have achieved our goal of declaring my global variables just once.
In extreme circumstances we can use expressions in #if lines:
#if ! defined(M_PI) && ! defined(PI) I don't know what pi is! #endif
If neither M_PI or PI has been #defined then the line "I don't know what pi is!" will get passed to the compiler with the inevitable consequence.
#defines can also take arguments, although these are less used than they used to be. For example;
#define myisdigit(c) (((c) >= '0' && (c) <= '9'))
notice that the "expansion" contains the arguments (in this case just one, "c"); this is nearly always the case. Now if our code contains the line:
if (myisdigit(str[i]))
It gets preprocessed to the macro definition with the"c"s replaced by "str[i]":
if ( (((str[i]) >= '0' && (str[i]) <= '9'))
Using this instead of isdigit() is very much faster on my machine. (It's also safer than it looks: although computers are not obliged to use ASCII to represent digits they are obliged to use a scheme where '1' == '0' + 1, etc.)
There are a few other dangers however, one of which is hinted at by the large number of brackets we chose to use.
#define BADSQ(foo) foo * foo
Then if the preprocessor later encounters:
z = BADSQ(x);
it replaces it with:
z = x * x;
This looks like a function but isn't, if the preprocessor later encounters:
z = BADSQ(x + y);
it replaces it with:
z = x + y * x + y;
which is not what we want! Even the following has a problem:
#define BADSQ(foo) (foo) * (foo)
As the line:
z = 1.0/BADSQ(x);
Expands to:
z = 1.0 /(x) * (x);Instead we should use:
#define SQ(foo) ((foo) * (foo))
Even this isn't entirely safe however, consider:
y = SQ(++x);
which becomes:
y = ((++x) * (++x));
So x gets incremented twice. We show this below: <>
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#define BADSQ(foo) foo * foo | ||||
#define BADSQ2(foo) (foo) * (foo) | ||||
#define SQ(foo) ((foo) * (foo)) | ||||
int main() { | int main() { | |||
double x = 1.2, y = 2.1; | double x = 1.2, y = 2.1; | |||
printf("%g squared is not %g\n", x+y, BADSQ(x+y) ); | printf("%g squared is not %g\n", x+y, x+y * x+y ); | |||
printf("1/%g squared is not %g\n", x, 1.0/ BADSQ2(x) ); | printf("1/%g squared is not %g\n", x, 1.0/ (x) * (x) ); | |||
printf("1.0/%g squared is %g\n", x+y, 1.0/ SQ(x+y) ); | printf("1.0/%g squared is %g\n", x+y, 1.0/ ((x+y) * (x+y)) ); | |||
printf("But %g squared ", x + 1); | printf("But %g squared ", x + 1); | |||
printf("is not %g\n", SQ(++x) ); | printf("is not %g\n", ((++x) * (++x)) ); | |||
printf("x is noq %g\n", x); | printf("x is noq %g\n", x); | |||
return 0; | return 0; | |||
} | } |
What this tells us is that using macros for mathematical shortcuts is very dubious: it's better to use a function instead.
Macros that use their arguments twice are best avoided and so is changing the value of variables in calls to functions or macro evaluations.
Macros with at least one argument may be specified to have a variable number of arguments: any "extra" arguments are just pasted in. These arguments are indicated by ...:
#define mymacro(arg1, arg2, ...)
We then need to specify where the arguments appear in the expansion. This is done by placing the word __VA_ARGS__ where the extra arguments should appear.
#ifdef DEBUG #define debug(format, ... ) fprintf(stderr, format, __VA_ARGS__) #else #define debug(format, ... ) #endif
We would normally put the above in a header file or at the very top of a source file. Inside a function we can then write:
debug("x = %g\n", x);
And it will only print out the message when DEBUG has been #defined.
We have already mentioned how C99 defines __func__, the name of the current function, and __LINE__ the line number. This enables to create a slightly better debug macro:
#ifdef DEBUG #define debug(format, ... ) fprintf(stderr, "%s() line %d: " format, __func__, __LINE__, __VA_ARGS__) #else #define debug(format, ... ) #endif
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#define DEBUG | ||||
#include "debug.h" | ||||
int main() { | int main() { | |||
int x = 1; | int x = 1; | |||
debug("x is %d\n", x); | fprintf(stderr, "%s() line %d: " "x is %d\n", __func__, 6, x | |||
return 0; | return 0; | |||
} | } |
The output is:
main() line 6: x is 1
If we remove the #define of debug:
Before preprocessing | After preprocessing | |||
---|---|---|---|---|
#include "debug.h" | ||||
int main() { | int main() { | |||
int x = 1; | int x = 1; | |||
debug("x is %d\n", x); | ; | |||
return 0; | return 0; | |||
} | } |
Preprocessor tricks can sometimes be useful but are always ugly. Use them sparingly: a few can be useful but like all bodges once we have too many they can be confusing and interact quite badly.