What's the quick and simple answer to last time's puzzle? There are actually two:
OK, the main subject for today: the preprocessor.
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 aren't 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 want 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. The upshot is that you don't have to go through your source file searching for all occurrences of (say) the 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=MULTIPLY(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.
OK, the above isn't 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 variable is a double because rand()/1e6 produces a floating point number.)
So 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 and 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.
#ifdef RISCOS static ZCONST char Far EnvUnZipExts[] = ENV_UNZIPEXTS; #endif
(This is 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, the snippet would be ignored totally. In fact, if it were 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 : : : #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 would be compiled.
The complement of #ifdef is #ifndef, for use if the macro name hasn't 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.
ANSI C has five macros predefined. The five are all succeeded and followed by two underscores:
__LINE__ __FILE__ __DATE__ __TIME__ __STDC__
To explain each...
__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 of hour : minute : second. __STDC__ is defined as the value 1 if the compiler conforms to ANSI standard.
Let me give you a couple of short examples to demonstrate these. Firstly...
#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 (a single line of text):
Compiling ADFS::Bluebottle.$.AcornC_C++.Learning.c.Examples .c. lines, line 6, on Jul 25 1999, at 23:16:35Here's 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; }
This would generate:
Compiling cline-program, line 101, on Jul 25 1999, at 23:18:27
# turns the function-like macro into a quoted string while ## concatenates 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 output.
#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 it's 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 as assert.h or swi.h).
#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. For example,
#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 isn't an ANSI standard one and then not compile the program.
That's enough for now. Next time, function pointers and memory allocations.
Source: | Archive Magazine 13.12 |
Publication: | Archive Magazine |
Contributor: | Paul Johnson |