C - From The Top (part 12)

What's the quick and simple answer to last time's puzzle? There are actually two:

  1. Create the seed file, and save this separately to the main encrypted file. For ease, make the first word in the file read 'encrypted'. The program 'sees' this word and loads the seed file. Decryption can then take place.
  2. Save the seed either at the start or at the end of the file. Whichever position you choose, a simple identifier block at the start of the seed will help (e.g. have the character ENC at the start of the seed.)

OK, the main subject for today: the preprocessor.

The preprocessor

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 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.

Conditional compilation

If you've ever had cause to examine source files of other people's C programs, you may find code looking like this:

#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.

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__

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:35
Here'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

# 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 ## 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 <> 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. 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