C from the top
12. Preprocessor-terous
Quick and simple answer to last times puzzle. There are actually two.
Okay, the main subjects for today - preprocessor.
The preprocessor
(The above picture is how the Acorn C front end application looks when the preprocess option is selected. Different compilers will not look like this.)
The preprocessor is a very handy part of the C language. It has a number of functions which you may have already seen if you've selected the preprocess only icon in the Acorn C front end program. What this does is to run through all the files that you've #include-d in your main source code, inserting the parts of the included file which are needed.
If the parts aren't there, the preprocessor tells you in the throwback screen.
The preprocessor also runs through the C file expanding on any function-like macros (and also macros which are not function like) which have been inserted to save you time.
For example
#define MAXADDR 300 #define LINELEN 60 char name [MAXADDR] [LINELEN]; int telephone [MAXADDR];
If you compiled this, the preprocessor would expand the char and telephone array sizes to read the values for MAXADDR and LINELEN. This has a couple of advantages. Firstly, if you wanted to make the database larger, you just change MAXADDR and secondly, any loop which searches all the database from 0 to MAXADDR-1 will now count to the new size of MAXADDR - upshot being that you don't have to go through your source file searching for all occurences of the (say) search loop.
A function like macro looks like this
#define MULTIPLY (a,b) a*b
and would be used in a program like this
int answer; answer=MULTIPY(6,9); printf("%d",answer);
The line answer=MULTIPLY(6,9) is converted to answer=6*9 by the preprocessor. These function like macros save the programmer (and usually also the program) a great deal of time in development.
Alright, the above is not much use, but consider this example
#define RANGE(i,min_val,max_val) (i<min_val) || (i>max_val) ?1:0 int main(void) { double r; do { r=rand()/1e6; } while (RANGE(r,1,100)); printf("%f",r); return 0; }
(remember, there are no spaces between RANGE and the brackets. Also the varible is a double as rand()/1e6 produces a floating point number)
Okay then, what's the program doing?
Firstly, we have set up a function like macro which has defined RANGE to take three variables - the random number, the max and minimum values and then compare the random number to these values. If the values fall outside this range, then a TRUE value is returned, otherwise a FALSE is returned.
There are a couple of problems with the function like macros.
Firstly, only simple operations can be included; you can't define an entire function in the function like macro. Secondly, as the code is duplicated on each call to the macro, the overall runtime file will be larger than if a normal function were used.
CONDITIONAL COMPILATION
If you've ever had cause to examine source files of other peoples C programs, you may find code looking like this :
#ifdef RISCOS static ZCONST char Far EnvUnZipExts[] = ENV_UNZIPEXTS; #endif
(this was taken from the Infozip Unzip.c source file.)
The #ifdef and #endif are both examples of a conditional compilation.
If the source file was to be compiled on a LINUX box, then the snippet would be ignored totally. Infact, if it was compiled under Acorn C it would be ignored as Acorn C defines __riscos and GCC defines __riscos__.
As with normal conditions, there are also conditional compilation versions of the same conditions :
#if
#else
#elif (else if)
#endif
#ifdef (if defined)
#ifndef (if not defined)
With these, you can create all sorts of ladders of preconditions :
#if expression-1 statements #elif expression-2 statements : and : so on : until ..... #endif
As soon as the first expression is TRUE, the lines associated with it are compiled and the rest skipped.
Again, if you had
#ifdef macro-name statements #endif
and macro-name had been defined, the code associated with the #ifdef will be compiled.
The compliment of #ifdef is #ifndef; if the macro name has not been defined previously. Here, you can define the macro.
A good example of using #ifdef is when debugging a program. The example on the cover disc (and websites) called debug shows how this is performed.
C's INBUILT MACROS
ANSI C has five macros predefined. The five are all succeeded and followed by two underscores __.
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
An explanation of these .......
__LINE__ defines an integer value which is the equivalent of the line number of the source file being compiled
__FILE__ defines a string that is the name of the file being compiled
__DATE__ defines a string holding the current system date. This can have the form of either month/day/year or day/month/year depending on which country you're in (though strictly speaking, it shouldn't).
__TIME__ defines a string containing the time at which compilation began. It has the form hour : minute : second.
__STDC__ is defined as the value 1 if the compiler conforms to ANSI standard.
A couple of short examples to demonstrate these
#include <stdio.h> int main(void) { printf("Compiling %s, line %d, on %s, at %s",__FILE__,__LINE__,__DATE__,__TIME__); return 0; }
IMPORTANT
All the inbuilt macros are defined and fixed at compile time.
The above example would display something like this
Compiling ADFS::Bluebottle.$.AcornC_C++.Learning.c.Examples.c.lines, line 6, on Jul 25 1999, at 23:16:35
A second example, this time with __LINE__ defined.
#include <stdio.h> int main(void) { #line 101 "cline-program" printf("Compiling %s, line %d, on %s, at %s\n",__FILE__,__LINE__,__DATE__,__TIME__); return 0; }
would generate
Compiling cline-program, line 101, on Jul 25 1999, at 23:18:27
# AND ##
These are the final parts of the preprocessor which I'll hit upon for now.
# turns the function like macro into a quoted string while ## concatentates two identifiers.
#include <stdio.h> #define MAKESTRING(str) # str int main(void) { int value=10; printf("%s is %d",MAKESTRING(value),value); return 0; }
The program returns "value is 10". Why, well, MAKESTRING was passed value. This was converted into a string which was outputted.
#include <stdio.h> #define output(i) printf("%d %d\n",i ## 1, i ## 2) int main(void) { int count1=10,count2=20; int i1=99,i2=-10 output(count) output(i); return 0; }
This program creates the macro output which is translated into a call to printf. The value of the two variables which end in 1 or 2 are displayed. This means when compiled and run we get 10 20 99 -10. You may not have thought this, after all, the call to output doesn't contain a defined variable.
What has happened is the macro has concatenated the number 1 or 2 onto the variable passed to give the correct variable names.
Further examples of these inbuilt macros can be seen if you look in any of the library files (such assert.h or swi.h).
#include <> or #include ""
Up until now, you will have only come across #include < .... >. This tells the compiler to search along a specific path until the file you've asked to be included has been found (in Acorn C, this is defined as C:, but you can define extra paths to search along).
#include "afile.h"
is not the same. When you create an application, you may decide to create a file which will contain (say) the variable required for the program or a structure containing the values for a SWI. For this, you will have a directory set up called h.
Your program will be stored in the c directory and will move up the tree one point (into the parent directory, e.g. $.) and then down into the h directory to load in the h file requested by the main source code.
e.g.
#include <stdio.h>
searches from C: until it finds the file stdio.h
#include "myvars.h"
searches the immediate h directory for the file myvars.h
The <> files are also the ANSI standard ones. Trying to compile one of your self created h files using the <> brackets will cause the compiler to complain that the file is not an ANSI standard one and then not compile the program.
That's enough for now. Next time, function pointers and memory allocations.
Download the examples files here