From: chris@gerty.equinox.gen.nz (Christopher Sawtell)
Subject: C Language Course - Syllabus
Message-ID: <1993Nov20.080424.2253@gerty.equinox.gen.nz>
Organization: Cogicorp
Date: Sat, 20 Nov 1993 08:04:24 GMT


                     Syllabus for the 'C' Language Course.

    1   a) Historical introduction to the Language.

        b) Demonstration of a very simple program.

        c) Brief explanation of how the computer turns
           your program text into an executing program.

        d) The basic differences between 'C' and other languages.
           The advantages and disadvantages.

      We make the assumption that you are able to turn on your machine,
      use the Operating System at the Control Line Interpreter prompt
      "$ ", "c:>" or whatever, and to use an editor to enter program text.


    2   a) How the 'C' language arranges for the storage of data.
           An explanation of the keywords associated with data.
           The storage classes:- static auto volatile const.
           The variable types:- char int long float double
           The meaning of:- signed unsigned

        b) Introduction to the concept of pointers.

        c) Explanation of reading from the keyboard and writing to the screen. 
           i.e. printf and scanf, the print formatted and scan formatted
           functions.

        d) The use of arguments to the main() function, argc argv env.

        e) A simple program to format text.


    3   Structures, arrays and pointers.

        a) Explanation of the more complex data structures.
        b) Programs which demonstrate uses of pointers.

    4   The operators of the language, arithmetic, pointer, logical, bitwise.

        a) Precedence.
        b) The unique bit and shifting operators.
           ( for a high level language )

    5   a) The Preprocessor.
        b) Header files

           What they are and what you put in them, both your own and 
           those provided by the 'C' compiler vendor.

           A simple title which includes all sorts of things,
           both very useful and a number of traps.

    6   The library, why we have them and some of the more useful routines.

        a) How to read the book.
        b) The string functions as an example.

    7   a) Mistakes and how avoid making them.
        b) Debugging strategies.
        c) The assert macro.

    8   a) The control structures of the language, what (not) to use and when.

    9   Some example programmes.
  
        a) An example of a doubly linked list.

   10   a) File IO

           This is an enormous subject and we we will
           really only just scratch on the surface.

   11   a) Lint, and more on errors / bugs and how to avoid them.

   12   The stack and a quick dip into assembler

        a) A study of the function calling mechanism used by most 'C'
           compilers and the effect on compiler output code of using 
           the register storage class and the optimiser.

   13   Dynamic Storage management on both the heap, and the stack.

        a) The 'heap', it's management, malloc(), realloc(), calloc(),
           and free().
        b) The alloca() storage allocation routine.


   14   Portability Issues.

        a) Defaults for storage sizes.
        b) 'endianism'. Yes, there are big-endian and little-endian computers!
        c) Functions which can be called with a variable number of arguments.


   15   More Sample programs.

        Much is to be gained from examining public domain packages
        examining the code and reviewing the author's style.
        We will look at a number of functions and complete packages.
        in particular we will examine a number of sorting functions,
        a multi-threading technique, queues, lists, hashing, and trees.
 

Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.

--------

                            Lesson One.

Some Historical Background.

The 'C' programming language was designed and developed by Brian Kernighan, and
Dennis Ritchie at The Bell Research Labs. 'C' is a Language specifically created
in order to allow the programmer access to almost all of the machine's internals
- registers, I/O slots and absolute addresses. However, at the same time, 
'C' allows for as much data hiding and programme text modularisation as is
needed to allow very complex multi-programmer projects to be constructed in an
organised and timely fashion. During the early 1960s computer Operating Systems
started to become very much more complex with the introduction of multi-terminal
and multi-process capabilities. Prior to this time Operating Systems had been
carefully and laboriously crafted using assembler codes, and many programming
teams realised that in order to have a working o/s in anything like a
reasonable time this was now longer economically feasible. This then was the
motivation to produce the 'C' Language, which was first implemented in
assembler on a Digital Equipment Corporation PDP-7. Of course once a simple
assembler version was working it was possible to rewrite the compiler in 'C'
itself. This was done in short order and therefore as soon as the PDP-11 was
introduced by DEC it was only necessary to change the code generator section
of the compiler and the new machine had a compiler in just a few weeks. 'C' was
then used to re-implement the UNIX o/s. This means, that a complete UNIX can be
transported, or to use the simple jargon of today; 'ported to a new machine in
literally just a few months by a small team of competent programmers.

Enough of the past. Lets see the various actions, or compilation phases,
through which the `C' compilation system has to go in order that your file
of `C' program text can be converted to a working program.

Assuming that you are able to work an editor and can enter a script
and create a file. Please enter the following tiny program.

#ident "@(#) Hello World - my first program"

#include <stdio.h>

char *format = "%s", 
     *hello = "Hello World...\n";

main()
{
  printf ( format, hello );
  }

Now save it in a file called hello.c. Lower case is allowed - encouraged, no
less - under the UNIX operating system.

Now type:

cc -o hello hello.c

The computer will apparently pause for a few moments and then the
Shell, or Command Line Interpreter prompt will re-appear.

Now type:

./hello

Lo and behold the computer will print

Hello World...

Let's just look at what the computer did during the little pause.

The first action is to activate a preliminary process called the pre-processor.
In the case of hello.c all it does is to replace the line 

#include <stdio.h>

with the file stdio.h from the include files library. The file stdio.h provides
us with a convenient way of telling the compiler that all the i/o functions
exist. There are a few other little things in stdio.h but they need not
concern us at this stage.

In order to see what the pre-processor actually outputs, you might like to
issue the command:

cc -P hello.c

The 'cc' command will activate the 'C' compilation system and the -P option
will stop the compilation process after the pre-processing stage, and another
file will have appeared in your directory. Have a look, find hello.i and use
the editor in view mode to have a look at it. So issue the command:

view hello.i

You will see that a number of lines of text have been added at the front of the 
hello.c program. What's all this stuff? Well, have a look in the file called
/usr/include/stdio.h again using the view command.

view /usr/include/stdio.h

Look familiar?

Now the next stage of getting from your program text to an executing program is
the compilation of your text into an assembler code program. After all that is
what a compiler is for - to turn a high level language script into an
assembler code program, which is, in turn, assembled into an object module
which is then linked to the various library routines to make the file
containing the image of the bit patterns of the instructions and data needed
by the computer in order that it can do that which you want it to do.

Lets see what happens next by issuing the command:-

cc -S hello.c

Once again there is another file in your directory - this time with a .s
suffix.

Lets have a look at it in the same way as the .i file

view hello.s

You will doubtless notice a few recognizable symbols and what appears to be a
pile of gibberish. The gibberish is in fact the nmemonics for the machine
instructions which are going to make the computer do what you have programmed
it to do.

Now this assembler code has to be turned into machine instructions.
To do this issue the command.

cc -g -c hello.s

Now, yet again there is another file in your directory - this time the suffix
is ".o". This file is called the object file. It contains the machine
instructions corresponding exactly to the nmemonic codes in the .s file.
If you wish you can look at these machine codes using one of the commands
available to examine object files.

dis -L -t .data hello.o >hello.dis

The output from these commands won't be very meaningful to you at this stage,
the purpose of asking you to use them is merely to register in your mind the
fact that an object file is created as a result of the assembly process.

The next stage in the compilation process is called by a variety of names -
"loading", "linking", "link editing". What happens is that the machine
instructions in the object file ( .o ) are joined to many more instructions
selected from an enormous collection of functions in a library. This phase of
the compilation process is invoked by the command:-

cc -o hello hello.o

Now, at last, you have a program to execute! So make it do it's thing by
putting the name of the executable file as a response to the Shell or Command
Line Interpreter prompt.

./hello

Presto, the output from your program appears on the screen.

Hello World...

You are now allowed to rejoice and have a nice warm fuzzy to hold!
You have successfully entered a `C' program, compiled it, linked it, and
finally, executed it!

Having gone through all the various stages of editing, pre-processing,
compiling, assembling, linking, and finally executing, by hand as it were, you
can now rest assured that all the stages are automated by the 'cc' command, and
you can forget how to invoke them! Just remember that the computer has to do
them in order for you to have a program to execute.

The single command you use to activate the C Compiler is:

cc -o hello hello.c

The word after the -o option is the name of the executable file, if you don't
provide a name here the compiler dreams up the name "a.out". The source file
MUST have the .c extension otherwise the compiler complains and stops working.

Notes:

   The command names used in the above text are those of standard UNIX,
   Your particular system may well use a different name for the 'C' compiler.
   bcc - for Borland 'C'.
   gcc - GNU 'C', which is standard on the Linux operating system.
   lc  - Lattice 'C', available on IBM and clone P.C.s as well as the Amiga.
   Check in the Documentation which came with your compiler.
   The same notions apply to the text editor.

Differences between 'C' and other languages.

In the years since 'C' was developed it has changed remarkable little.
This fact is a bouquet to the authors, who had the vision and understanding to
create a language which has endured so well. The strengths and weaknesses
should be pointed out here.

The big plus is that it is possible to do everything ( well at least 99.9% ) in
'C' while other languages compel you to write a procedure, subroutine or
function in assembler code.

'C' has very good facilities for creating tables of constant data within the
source file.

'C' doesn't do very much to protect you from yourself. This means that the
resulting code executes faster than most other high level languages, but a much
greater degree of both care and understanding is demanded from the programmer.

'C' is not a closely typed language, although the newer compilers are offering
type checking as part of the language itself as opposed to having to use a
separate program for mechanised debugging.

'C' is a small language with very few intrinsic operations. All the heavy work
is done by explicit library function calls.

'C' allows you to directly and conveniently access most of the internals of
the machine ( the memory, input output slots, and CPU registers ) from the
language without having to resort to assembler code.

'C' compilers have an optimisation phase which can be invoked if desired.
The output code can be optimised for either speed or memory usage. The code
will be just as good as that produced by an assembly code programmer of normal
skill - with much effort real guru programmers can do somewhat better.


Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                  Lesson 2

                            Data Storage Concepts.

  It has been stated that "data + algorithms = programs".
  This Lesson deals with with the first part of the addition sum.

  All information in a computer is stored as numbers represented using the
binary number system. The information may be either program instructions or
data elements. The latter are further subdivided into several different types,
and stored in the computer's memory in different places as directed by the
storage class used when the datum element is defined.

These types are:

  a) The Character.

     This is a group of 8 data bits and in 'C' represents either
     a letter of the Roman alphabet, or a small integer in the range of 0
     through to +255. So to arrange for the compiler to give you a named
     memory area in which to place a single letter you would "say":

  char letter;

     at the beginning of a program block. You should be aware that
     whether or not a char is signed or unsigned is dependent
     on the design of the processor underlying your compiler.
     In particular, note that both the PDP-11, and VAX-11 made by
     Digital Equipment Corporation have automatic sign extension of char.
     This means that the range of char is from -128 through to +127
     on these machines. Consult your hardware manual, there may be
     other exceptions to the trend towards unsigned char as the default.

     This test program should clear things up for you.

/* ----------------------------------------- */

#ident "@(#) - Test char signed / unsigned.";

#include <stdio.h>

main()
{
  char a;
  unsigned char b;

  a = b = 128;
  a >>= 1;
  b >>= 1;
  printf ( "\nYour computer has %ssigned char.\n\n", a == b ? "un" : "" );
  }

/* ----------------------------------------- */

     Here ( Surprise! Surprise! ) is its output on a machine which has
     unsigned chars.

Your computer has unsigned char.

    Cut this program out of the news file. Compile and execute it on
    your computer in order to find out if you have signed or unsigned char.

  b) The Integers.

     As you might imagine this is the storage type in which to store whole
     numbers. There are two sizes of integer which are known as short and long.
     The actual number of bits used in both of these types is Implementation
     Dependent. This is the way the jargonauts say that it varies from computer
     to computer. Almost all machines with a word size larger than sixteen bits
     have the the long int fitting exactly into a machine word and a short int
     represented by the contents of half a word. It's done this way because
     most machines have instructions which will perform arithmetic efficiently
     on both the complete machine word as well as the half-word. For the
     sixteen bit machines, the long integer is two machine words long,
     and the short integer is one.

  short int smaller_number;
  long int big_number;

     Either of the words short or long may be omitted as a default is
     provided by the compiler. Check your compiler's documentation to see
     which default you have been given. Also you should be aware that some
     compilers allow the you to arrange for the integers declared with just
     the word "int" to be either short or long. The range for a short int on
     a small computer is -32768 through to +32767, and for a long int
     -4294967296 through to +4294967295.
 
  c) The Real Numbers.

     Sometimes known as floating point numbers this number representation
     allows us to store values such as 3.141593, or -56743.098. So, using
     possible examples from a ship design program you declare floats and
     doubles like this:

  float length_of_water_line;     /* in meters */
  double displacement;            /* in grammes */

     In the same way that the integer type offers two sizes so does the
     floating point representation. They are called float and double. Taking
     the values from the file /usr/include/values.h the ranges which can be
     represented by float and double are:

  MAXFLOAT  3.40282346638528860e+38
  MINFLOAT  1.40129846432481707e-45
  MAXDOUBLE  1.79769313486231470e+308
  MINDOUBLE  4.94065645841246544e-324

     However you should note that for practical purposes the maximum
     number of significant digits that can be represented by a float
     is approximately six and that by a double is twelve. Also you should
     be aware that the above numbers are as defined by the IEEE floating
     point standard and that some older machines and compilers do not
     conform. All small machines bought retail will conform. If you are
     in doubt I suggest that refer to your machine's documentation for
     the whole and exact story!


  d) Signed and unsigned prefixes.

     For both the character and integer types the declaration can be
     preceded by the word "unsigned". This shifts the range so that 0
     is the minimum, and the maximum is twice that of the signed data
     type in question. It's useful if you know that it is impossible
     for the number to go negative. Also if the word in memory is going
     to be used as a bit pattern or a mask and not a number the use of
     unsigned is strongly urged. If it is possible for the sign bit in
     the bit pattern to be set and the program calls for the bit pattern
     to be shifted to the right, then you should be aware that the sign
     bit will be extended if the variable is not declared unsigned.
     The default for the "int" types is always "signed", and, as discussed
     above that of the "char" is machine dependent.
  
  This completes the discussion on the allocation of data types, except to
  say that we can, of course, allocate arrays of the simple types simply by
  adding a pair of square brackets enclosing a number which is the size of
  the array after the variable's name:

  char client_surname[31];   

  This declaration reserves storage for a string of 30 characters plus the
  NULL character of value zero which terminates the string.

  Structures.

   Data elements which are logically connected, for example - to use the
   example alluded to above - the dimensions and other details about a sea
   going ship, can be collected together as a single data unit called a
   struct. One possible way of laying out the struct in the source code is:
     
struct ship          /* The word "ship" is known as the structure's "tag". */
{
  char name[30];
  double displacement;                           /* in grammes */
  float length_of_water_line;                    /* in meters */
  unsigned short int number_of_passengers;
  unsigned short int number_of_crew;
  };

     Note very well that the above fragment of program text does NOT
     allocate any storage, it merely provides a named template to the
     compiler so that it knows how much storage is needed for the
     structure. The actual allocation of memory is done either like this:

struct ship cunarder;

     Or by putting the name of the struct variable between the "}" and
     the ";" on the last line of the definition. Personally I don't
     use this method as I find that the letters of the name tend to get
     "lost" in the - shall we say - amorphous mass of characters which
     make up the definition itself.

     The individual members of the struct can have values assigned to
     them in this fashion:

  cunarder.displacement = 97500000000.0;
  cunarder.length_of_water_line = 750.0
  cunarder.number_of_passengers = 3575;
  cunarder.number_of_crew = 4592;

     These are a couple of files called demo1.c & demo1a.c which contain
     small 'C' programs for you to compile. So, please cut them out of the
     news posting file and do so.

 /* -------------------------------cut here--------------------------------- */

#ident demo1.c  /* If your compiler complains about this line, chop it out */
#include <stdio.h>

struct ship
{
  char name[31];
  double displacement;                              /* in grammes */
  float length_of_water_line;                       /* in meters */
  unsigned short int number_of_passengers;
  unsigned short int number_of_crew;
  };

char *format = "\
Name of Vessel: %-30s\n\
  Displacement: %13.3f\n\
    Water Line: %5.1f\n\
    Passengers: %4d\n\
          Crew: %4d\n\n";

main()
{
  struct ship cunarder;

  cunarder.name = "Queen Mary";                  /* This is the bad line. */
  cunarder.displacement = 97500000000.0;
  cunarder.length_of_water_line = 750.0
  cunarder.number_of_passengers = 3575;
  cunarder.number_of_crew = 4592;

  printf ( format,
           cunarder.name,
           cunarder.displacement, 
           cunarder.length_of_water_line,
           cunarder.number_of_passengers,
           cunarder.number_of_crew
           );
  }

 /* -------------------------------cut here--------------------------------- */

     Why is the compiler complaining at line 21?
     Well C is a small language and doesn't have the ability to allocate
     strings to variables within the program text at run-time. This
     program shows the the correct way to copy the string "Queen Mary",
     using a library routine, into the structure.

 /* -------------------------------cut here--------------------------------- */

#ident demo1a.c  /* If your compiler complains about this line, chop it out */
#include <stdio.h>

/*
** This is the template which is used by the compiler so that
** it 'knows' how to put your data into a named area of memory.
*/

struct ship
{
  char name[31];
  double displacement;                              /* in grammes */
  float length_of_water_line;                       /* in meters */
  unsigned short int number_of_passengers;
  unsigned short int number_of_crew;
  };

/*
** This character string tells the printf() function how it is to output 
** the data onto the screen. Note the use of the \ character at the end
** of each line. It is the 'continue the string on the next line' flag
** or escape character. It MUST be the last character on the line.
** This technique allows you to produce nicely formatted reports with all the
** ':' characters under each other, without having to count the characters
** in each character field.
*/

char *format = "\n\
Name of Vessel: %-30s\n\
  Displacement: %13.1f grammes\n\
    Water Line: %5.1f metres\n\
    Passengers: %4d\n\
          Crew: %4d\n\n";

main()
{
  struct ship cunarder;

  strcpy ( cunarder.name, "Queen Mary" );           /* The corrected line */
  cunarder.displacement = 97500000000.0;
  cunarder.length_of_water_line = 750.0;
  cunarder.number_of_passengers = 3575;
  cunarder.number_of_crew = 4592;

  printf ( format,
           cunarder.name,
            cunarder.displacement, 
           cunarder.length_of_water_line,
           cunarder.number_of_passengers,
           cunarder.number_of_crew
            );
  }

 /* -------------------------------cut here--------------------------------- */

     I'd like to suggest that you compile the program demo1a.c and execute it.

$ cc demo1a.c
$ a.out
     
Name of Vessel: Queen Mary                       
  Displacement: 97500000000.0 grammes
    Water Line: 750.0 metres
    Passengers: 3575
          Crew: 4592

     Which is the output of our totally trivial program to demonstrate
     the use of structures.

  Tip:

     To avoid muddles in your mind and gross confusion in other minds
     remember that you should ALWAYS declare a variable using a name
		 which is long enough to make it ABSOLUTELY obvious what you are
		 talking about.

  Storage Classes.

  The little dissertation above about the storage of variables was
  concerned with the sizes of the various types of data. There is
  just the little matter of the position in memory of the variables'
  storage.

  'C' has been designed to maximise the the use of memory by allowing you
  to re-cycle it automatically when you have finished with it.
  A variable defined in this way is known as an 'automatic' one. Although
  this is the default behaviour you are allowed to put the word 'auto' in
  front of the word which states the variable's type in the definition.
  It is quite a good idea to use this so that you can remind yourself
  that this variable is, in fact, an automatic one. ( I nearly always
	forget! ).
	There are three other storage allocation methods, 'static' and 'register',
	and 'const'. The 'static' method places the variable in main storage
	for the whole of the time your program is executing. In other words
	it kills the 're-cycling' mechanism. This also means that the value
	stored there is also available all the time. The 'register' method
	is very machine and implementation dependent, and also perhaps somewhat
	archaic in that the optimiser phase of the compilation process does
	it all for you. 'const' is just what you would think it is. The compiler
	ejects an error message if you try to assign a value to a 'const',
	also of course it, the compiler will eject one of the more efficient
	immediate instructions instead of a memory reference instruction.

	  For the sake of completeness I'll explain. Computers have a small
  number of places to store numbers which can be accessed very quickly.
  These places are called the registers of the Central Processing Unit.
  The 'register' variables are placed in these machine registers instead of
  stack or main memory. For program segments which are tiny loops the speed
  at which your program executes can be enhanced quite remarkably.
  The optimiser compilation phase places as many of your variables into
  registers as it can. However no machine can decide which of the variables
  should be placed in a register, and which may be left in memory, so if
  your program has many variables and two or three should be register ones
  then you should specify which ones to the compiler.

  All this is dealt with at much greater detail later in the course.

  Pointers.

  'C' has the very useful ability to set up pointers. These are memory
  cells which contain the address of a data element. The variable name is
  preceded by a '*' character. So, to reserve an element of type char and
  a pointer to an element of type char, one would say.

char c;
char *ch_p;

  I always put the suffix '_p' on the end of all pointer variables
  simply so that I can easily remember that they are in fact pointers.

  There is also the companion unary operator '&' which yields the
  address of the variable. So to initialise our pointer ch_p to point
  at the char c, we have to say.

ch_p = &c;

  Note very well that the process of indirection can proceed to any
  desired depth, However it is difficult for the puny brain of a normal
  human to conceptualise and remember more that three levels! So be careful
  to provide a very detailed and precise commentary in your program if
  you put more than two or three stars.


  Getting data in and out of your programs.

  As mentioned before 'C' is a small language and there are no intrinsic
  operators to either convert between binary numbers and ascii
  characters or to transfer information to and fro between the
  computer's memory and the peripheral equipment, such as terminals or
  disk stores.

  This is all done using the i/o functions declared in the file stdio.h 
  which you should have examined earlier. Right now we are going to look
  at the functions "printf" and "scanf". These two functions together
  with their derivatives, perform i/o to the stdin and stdout files,
  i/o to nominated files, and internal format conversions. This means
  the conversion of data from ascii character strings to binary numbers
  and vice versa completely within the computer's memory. It's more
  efficient to set up a line of print inside memory and then to send the
  whole line to the printer, terminal, or whatever, instead of
  "squirting" the letters out in dribs and drabs!

  Study of them will give you understanding of a very convenient way to
  talk to the "outside world".

  So, remembering that one of the most important things you learn in
  computing is "where to look it up", lets do just that.
  If you are using a computer which has the unix operating system,
  find your copy of the "Programmer Reference Manual" and turn to the
  page printf(3S), alternatively, if your computer is using some other
  operating system, then refer to the section of the documentation which
  describes the functions in the program library.

  You will see something like this:-

  NAME
        printf, fprintf, sprintf - print formatted output.

  SYNOPSIS
        #include <stdio.h>

        int printf ( format [ , arg ] ... )
        char *format;

        int fprintf ( stream, format [ , arg ] ... )
        FILE *stream;
        char *format;

        int sprintf ( s, format [ , arg ] ... )
        char *s, *format;

  DESCRIPTION

        etc... etc... 

  The NAME section above is obvious isn't it?

  The SYNOPSIS starts with the line #include <stdio.h>. This tells
  you that you MUST put this #include line in your 'C' source code
  before you mention any of the routines. The rest of the paragraph
  tells you how to call the routines. The " [ , arg ] ... " hieroglyph
  in effect says that you may have as many arguments here as you wish,
  but that you need not have any at all.

  The DESCRIPTION explains how to use the functions.

  Important Point to Note:

  Far too many people ( including the author ) ignore the fact that
  the printf and scanf families of functions return a useful number
	which can be used to check that the conversion has been done correctly,
	and that the i/o operation has been completed without error.

  Refer to the format string in the demonstration program above for
  an example of a fairly sophisticated formatting string. 

  In order to fix the concepts of printf in you mind, you
  might care to write a program which prints some text in three ways:

a) Justified to the left of the page. ( Normal printing. )
b) Justified to the right of the page.
c) Centred exactly in the middle of the page.

  Suggestions and Hint.

  Set up a data area of text using the first verse of "Quangle" as data.
  Here is the program fragment for the data:-

 /* -------------------------------cut here--------------------------------- */

char *verse[] =
{
  "On top of the Crumpetty Tree",
  "The Quangle Wangle sat,",
  "But his face you could not see,",
  "On account of his Beaver Hat.",
  "For his Hat was a hundred and two feet wide.",
  "With ribbons and bibbons on every side,",
  "And bells, and buttons, and loops, and lace,",
  "So that nobody ever could see the face",
  "Of the Quangle Wangle Quee.",
  NULL
  };

 /* -------------------------------cut here--------------------------------- */

  Please cut this out of the news file and put it into a file named verse.c

  Now write a main() function which uses printf alone for (a) & (b)
  You can use both printf() and sprintf() in order to create 
  a solution for (c) which makes a good use of the capabilities
  of the printf family. The big hint is that the string controlling
  the format of the printing can change dynamically as program execution
  proceeds. A possible solution is presented in the file verse.c which is
  appended here. I'd like to suggest that you have a good try at making
  a program of you own before looking at my solution.
  ( One of many I'm sure )

 /* -------------------------------cut here--------------------------------- */

#include <stdio.h>

char *verse[] =
{
  "On top of the Crumpetty Tree",
  "The Quangle Wangle sat,",
  "But his face you could not see,",
  "On account of his Beaver Hat.",
  "For his Hat was a hundred and two feet wide.",
  "With ribbons and bibbons on every side,",
  "And bells, and buttons, and loops, and lace,",
  "So that nobody ever could see the face",
  "Of the Quangle Wangle Quee.",
  NULL
  };

main()
{
  char **ch_pp;

  /*
  ** This will print the data left justified.
  */

  for ( ch_pp = verse; *ch_pp; ch_pp++ ) printf ( "%s\n", *ch_pp );
  printf( "\n" );

  /*
  ** This will print the data right justified.
  **
  **  ( As this will print a character in column 80 of
  **    the terminal you should make sure any terminal setting
  **    which automatically inserts a new line is turned off. )
  */

  for ( ch_pp = verse; *ch_pp; ch_pp++ ) printf ( "%79s\n", *ch_pp );
  printf( "\n" );

  /*
  ** This will centre the data.
  */

  for ( ch_pp = verse; *ch_pp; ch_pp++ )
  {
    int length;
    char format[10];
    
    length = 40 + strlen ( *ch_pp ) / 2;      /* Calculate the field length  */
    sprintf ( format, "%%%ds\n", length );    /* Make a format string.       */
    printf ( format, *ch_pp );                /* Print line of verse, using  */
    }                                         /* generated format string     */
  printf( "\n" );
  }

 /* -------------------------------cut here--------------------------------- */

  If you cheated and looked at my example before even attempting
  to have a go, you must pay the penalty and explain fully why
  there are THREE "%" signs in the line which starts with a call
  to the sprintf function. It's a good idea to do this anyway!

  So much for printf(). Lets examine it's functional opposite - scanf(),

  Scanf is the family of functions used to input from the outside world
  and to perform internal format conversions from character strings to
  binary numbers. Refer to the entry scanf(3S) in the Programmer
  Reference Manual. ( Just a few pages further on in the book from printf. )

  The "Important Point to Note" for the scanf family is that the 
  arguments to the function are all POINTERS. The format string has to
  be passed in to the function using a pointer, simply because this
  is the way 'C' passes strings, and as the function itself has to store
  its results into your program it ( the scanf function ) has to "know"
  where you want it to put them. Once again you get a very useful
	number returned by the scanf. The number of items taken from the
	input string and successfully converted. You can use this feature to
	check that the user has typed meaningful data.


Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                    Lesson 3

                               Arrays and Pointers.

   You can allocate space for an array of elements at compile time with fixed
   dimension sizes of any data type, even functions and structs.
   So these are legal array definitions:

  char name[30];                   /* An array of 30 signed characters. */
  char *strings[50];               /* 50 pointers to strings. */
  unsigned long int *(*func)()[20];/* An array of pointers to functions which */
                                   /* return pointers to unsigned long ints. */
   
   You can declare a pointer to point at any type of data element, and as in
   the array situation above functions and structs are included.

struct ship
{
  char name[30];
  double displacement;                           /* in grammes */
  float length_of_water_line;                    /* in meters */
  unsigned short int number_of_passengers;
  unsigned short int number_of_crew;
  };

   So using the ship concept from Lesson 2 you can declare a pointer to point
   at one of the ship structs in an array.

struct ship *vessel_p;

   Note the use of the suffix "_p".
   This is my way of reminding myself that the variable is a pointer.

struct ship fleet[5];     /* This allocates enough storage for 5 ships' info. */

   Now lets set the pointer to point at the first vessel in the fleet.

  vessel_p = fleet;

   This pointer can be made to point at other ships in the fleet by
   incrementing it or doing additive arithmetic on it:

  vessel_p++;             /* point a the next ship in the fleet array. */
  vessel_p = fleet + 3;

   Also we can find out the index of the ship in the fleet at which we are
   pointing:

  i = vessel_p - fleet;
   
   It is also legal to find out the separation of two pointers pointing at
   elements in an array:

  d = vessel_p - another_vessel_p; /* This gives the separation in elements. */

   So summarising, pointers may be incremented, decremented, and subtracted
   one from another or have a constant added or subtracted from them.
	 Any other mathematical operation is meaningless and not allowed.

   Assembler programmers should note that while the pointer variables contain a
   byte machine address, when the arithmetic is done using pointers the compiler
   also issues either a multiply or a divide as well as the add or subtract
   instruction so that the result is ALWAYS expressed in elements rather than
   bytes. Have a go and write yourself a trivial little program, and have a
   look at the compiler output code. Lesson 1 told you how!

   When using a pointer to reference a structure we have to use a "pointer
   offset" operator in order to access the member of the struct we require:

  vessel_p = fleet;

  vessel_p->name = "Queen Mary";
  vessel_p->displacement = 97500000000.0;
  vessel_p->length_of_water_line = 750.0
  vessel_p->number_of_passengers = 3575;
  vessel_p->number_of_crew = 4592;

   Remember:
       
       It's a "." when accessing a struct which is in storage declared in
       the program.

       It's a "->" when accessing a struct at which a pointer is pointing.

  Initialisation of arrays.

   'C' has the facility to initialise variables in a program script.

   Some examples:

  char *qbf = "The quick brown fox jumped over the lazy dogs back";

  int tic_tac_toe[3][3] =
  {
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
    };

  struct ship fleet[2] =
  {
    { "Queen Elizabeth",  97500000000.0, 750.0, 3575, 4592 },
    {      "Queen Mary", 115000000000.0, 875.0, 4500, 5500 }
    };

   Take a careful note of where the commas and semi-colons go ( and don't go )!

   Initialised Tables of Indeterminate Length.

   One nice feature 'C' offers is that it is able to calculate
   the amount of storage required for a table by 'looking' at the number
   of initialisers.

char *verse[] =
{
  "On top of the Crumpetty Tree",
  "The Quangle Wangle sat,",
  "But his face you could not see,",
  "On account of his Beaver Hat.",
  "For his Hat was a hundred and two feet wide.",
  "With ribbons and bibbons on every side,",
  "And bells, and buttons, and loops, and lace,",
  "So that nobody ever could see the face",
  "Of the Quangle Wangle Quee."
  NULL
  };

   Note the * character in the definition line. This means that we are going
   to make an array of pointers to variables of type char. As there is no
   number between the [ ] characters the compiler calculates it for us.
   With this kind of set-up it is nice and easy to add extra information
   to the table as program development proceeds. The compiler will calculate
   the new dimension for you. The point to remember is that the program has to
   know - from the contents of the table - that it has come to the end of the
   table! So you have to make a special entry which CANNOT under any
   circumstances be a real data element. We usually use NULL for this.
   The other way is to calculate the size of the table by using the sizeof
   operator - Note that although use of sizeof looks like a function call
   it is in fact an intrinsic operator of the language. The result is
   available at compile time. So to obtain the size of the array of pointers
	 in bytes one can say:-

#define SIZE_OF_VERSE ( sizeof ( verse ))

   Now that on its own is not very useful, we really want the size in
	 elements - for use in "for" loops etc. - so we can say:-

#define VERSE_NO_ELEMENTS sizeof (( verse ) / sizeof ( verse[0] ))

   There is one final initialised data type, the enum. It is a fairly recent
   addition to the language.

  enum spectrum { red, orange, yellow, green, blue, indigo, violet } colour;

   In this construct the first symbol is given the value of 0 and for each
   following symbol the value is incremented. It is however possible to assign
   specific values to the symbols like this:

  enum tub
  { anorexic = 65,
    slim = 70,
    normal = 80,
    fat = 95,
    obese = 135
    };

   Some compilers are bright enough to detect that it is an error if an
   attempt is made to assign a value to an enum variable which is not in
   the list of symbols, on the other hand many are not. Take care! In
   practice there is little difference between the enum language construct
   and a number of define statements except perhaps aesthetics. Here is
   another trivial program which demonstrates the use of enum and a
   pre-initialised array.

 /* -------------------------------cut here--------------------------------- */

#include <stdio.h>

enum spectrum { red, orange, yellow, green, blue, indigo, violet } colour;

char *rainbow[] = { "red", "orange", "yellow", "green",
                    "blue", "indigo", "violet" };

main()
{
  for ( colour = red; colour <= violet; colour++ )
  {
    printf ( "%s ", rainbow[colour]);
    }
  printf ( "\n" );
  }

 /* -------------------------------cut here--------------------------------- */

   The output of which is ( not surprisingly ):

red orange yellow green blue indigo violet 

   One quite advanced use of initialised arrays and pointers is the jump or
   dispatch table. This is a efficient use of pointers and provides a very much
   better ( In my opinion ) method of controlling program flow than a maze
   of case or ( heaven forbid ) if ( ... ) goto statements.

   Please cut out this program, read and compile it.

 /* -------------------------------cut here--------------------------------- */

char *ident = "@(#) tellme.c - An example of using a pointer to a function.";

#include <stdio.h>      
#include <math.h>
#include <sys/errno.h>

/*
These declarations are not in fact needed as they are all declared extern in
math.h. However if you were to use routines which are not in a library and
therefore not declared in a '.h' file you should declare them. Remember you
MUST declare external routines which return a type other than the int type.

extern double  sin ();
extern double  cos ();
extern double  tan ();
extern double atof ();
*/

struct table_entry
{
  char *name;                        /* The address of the character string. */
  double (*function)();   /* The address of the entry point of the function. */
  };

typedef struct table_entry TABLE;

double help ( tp )
TABLE *tp;
{ printf ( "Choose one of these functions:- " );
  fflush ( stdout );
  for ( ; tp -> name; tp++ ) printf ( "%s ", tp -> name );
  printf ( "\nRemember the input is expressed in Radians\n" );
  exit ( 0 );
  return ( 0.0 );  /* Needed to keep some nit-picking dumb compilers happy! */ 
  }

/*
** This is the array of pointers to the strings and function entry points.
** Is is initialised at linking time. You may add as many functions as you 
** like in here PROVIDED you declare them to be extern, either in some .h 
** file or explicitly.
*/

TABLE interpretation_table [ ] =  
{
  { "sin",  sin  },
  { "tan",  tan  },
  { "cos",  cos  },
  { "help", help },
  {  NULL,  NULL }               /* To flag the end of the table. */
  };

char *output_format = { "\n %s %s = %g\n" };
extern int errno;
extern void perror();

main( argc, argv )
int argc;
char **argv;
{
  TABLE *tp;
  double x, answer;

  if ( argc > 3 )
  {
    errno = E2BIG;
    perror ( "tellme" );
    exit ( -1 );
    }

  for (;;)                  /* This is the way to set up a continuous loop. */
  { 
    for ( tp = interpretation_table;
          ( tp -> name && strcmp ( tp -> name, argv[1] )); 
          tp++
          ) ;                 /* Note use of empty for loop to position tp. */

    if ( tp -> function == help ) (*tp -> function )( interpretation_table );

    if ( tp -> name == NULL )
    { 
      printf ( "Function %s not implemented yet\n", argv[1] );
      exit ( 1 );
      }
    break;                     /* Leave the loop. */
    }

  x = atof ( argv[2] );        /* Convert the character string to a double. */
  answer = ( *tp -> function )( x );/* Execute the desired function.        */
  printf ( output_format,      /* Pointer to printf()'s format string.      */
           argv[1],            /* Pointer to the name of the function.      */
           argv[2],            /* Pointer to the input number ascii string. */
           answer              /* Value ( in double floating point binary ) */
           );
  }

 /* -------------------------------cut here--------------------------------- */

Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
and storage in electronic data retrieval systems, but not
modification, of this course and its use for personal
study only, provided all the copyright notices are left in the 
text and are printed in full on any subsequent paper reproduction.
In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                    Lesson 4.

                         The operators of the language.

  I have mentioned that 'C' is a small language with most of the heavy work
being done by explicit calls to library functions. There is however a rich
mix of intrinsic operators which allow you to perform bit level operations,
use pointers, and perform immediate operations on varables. In other words,
most of a machine's instruction set is able to be used in the object program.
At the time when 'C' was designed and first written these were unique
features for a high level language.

  Lets start with a discussion about precedence.

  This really means that the compiler puts invisable parentheses into
your expression. Casting your mind back to Arithmetic in the primary school
I expect you remember the nmemonic "My Dear Aunt Sally". The 'C' language
does as well! So the following expression is correct

  15 + 4 * 11 = 59

  The compiler has rendered the expression as:

  15 + ( 4 * 11 ) = 59

  Now the 'C' language has a much larger collection of operators than just
Multiply Divide Add Subtract, in fact much too big to try to remember the
precedence of all of them. So my recomendation is to ALWAYS put in the
parentheses, except for simple arithmetic. However, for the sake of
completeness as much as anything else, here is the list.

  First up come what are called the primary-expression operators:

    ()    Function.
    []    Array.
    .     struct member ( variable ).
    ->    struct member ( pointer ).
  
   The unary operators:

    *     Indirection via a Pointer.
    &     Address of Variable.
    -     Arithmetic Negative.
    !     Logical Negation or Not.
    ~     Bit-wise One's Complement.
    ++    Increment.
    --    Decrement.
    sizeof  Which is self explanitary.

  Now the binary operators:

   Arithmetic Operators.
      
    *     Multiply.                                       My
    /     Divide.                                         Dear
    %     Modulo, or Remainder of Integer Division.
    +     Addition.                                       Aunt
    -     Subtraction.                                    Sally

   The Shifting Operators.

    >>    Bit-wise Shift to the Right.
    <<    Bit-wise Shift to the Left.

   Logical Relation Operators.

    <     Less Than.
    >     Greater Than.
    <=    Less Than or Equal.
    >=    Greater Than or Equal.
    ==    Equal.
    !=    Not Equal.

   Bit-wise Boolean Operators.

    &     Bit-wise And.
    ^     Bit-wise Exclusive-or.
    |     Bit-wise Or.
   
   The Logical Operators.

    &&    Logical And.
    ||    Logical Or.

   The Assignment Operators. ( They all have the same priority. )

    =     The normal assignment operator.

   The Self-referencing Assignment Operators.

    +=
    -=
    *=
    /=
    %=
    >>=
    <<=
    &=
    ^=
    |=

  Some explanation is in order here. The machine instructions in your
computer include a suit of what are called "immediate operand" instructions.
These instructions have one of the operands in a register and the other
is either part of the instruction word itself ( if it is numerically small
enough to fit ) or is the next word in the address space "immediately" after
the instruction code word. 'C' makes efficient use of this machine feature 
by providing the above set of operations each of which translates directly
to its corresponding machine instruction. When the variable in question is a
'register' one, or the optimiser is in use, the compiler output is just
the one "immediate" machine instruction. Efficiency Personified!!!

  These two lines will make things clearer.

  a = 8;
  a += 2;     /* The result is 10 */

  The exclusive-or operation is very useful. You can toggle any combination
of bits in the variable using it.

  a = 7;
  a ^= 2;    /* Now a is 5 */
  a ^= 2;    /*  and back to 7. */

  Naturally, you can use the other operations in exactly the same way,
I'd like to suggest that you make a utterly simplistic little program
and have a look at the assembler code output of the compiler. Don't be
afraid of the assembler codes - they don't bite - and you will see
what I was on about in the paragraph above.

  Historical Note and a couple of Cautions.

  In the Olden Days when 'C' was first written all the self-referencing
operations had the equals symbol and the operand around the other way.
Until quite recently ( unix system V release 3.0 ) the 'C' compiler had a
compatability mode and could cope with the old style syntax. 

  A sample or test program is probably in order here.

 /* -------------------------------cut here--------------------------------- */

#include <stdio.h>

char *mes[] =
{
  "Your compiler",
  " understands",
  " does not understand",
  " the old-fashioned self-referencing style."
  };

main()
{
  int a;
  
  a = 5;
  a=-2;
  printf ( "%s %s %s\n", mes [ 0 ], mes [ ( a == -2 ) ? 2 : 1 ], mes [ 3 ] );
  }

 /* -------------------------------cut here--------------------------------- */

  The 'C' compiler issued with unix System V release 3.2 seems to have
( thankfully ) dropped the compatability mode. However a collegue, who
was using an old compiler, and I spent hours trying to find this strange bug!
The cure for the problem is either to put spaces on either side of the '=' sign
or to bracket the unary minus to the operand.

  a=(-2);
  a = -2;

Either is acceptable, and might save you a lot of spleen if sombody tries
to install your work of art program on an ancient machine.

  The other caution is the use of the shifting instructions with signed
and unsigned integers.

  If you shift a signed integer to the right when the sign bit is set
then in all probability the sign will be extended. Once again a little
demo program. Please cut it out of the news file with your editor
and play with it.

 /* -------------------------------cut here--------------------------------- */

#ident "#(@) shifts.c - Signed / Unsigned integer shifting demo."
#include <stdio.h>

#define WORD_SIZE ( sizeof ( INTEGER int ) * 8 )
#define NIBBLE_SIZE 4
#define NIBBLES_IN_WORD (( WORD_SIZE ) / NIBBLE_SIZE )
#define SIGN_BIT ( 1 << ( WORD_SIZE - 1 ))

char *title[] =
{ "       Signed             Unsigned",
  "                 Signed                                 Unsigned"
  };

main ()
{
  INTEGER int a;
  unsigned INTEGER int b, mask;
  int ab, i, j, bit_counter, line_counter;

  a = b = SIGN_BIT;
  printf ( "%s\n\n", title [ ( WORD_SIZE == 16 ) ? 0 : 1 ] );

  for ( line_counter = 0; line_counter < WORD_SIZE; line_counter++ )
  {
    for ( ab = 0; ab < 2; ab++ )
    {
      mask = SIGN_BIT;
      for ( i = 0; i < NIBBLES_IN_WORD; i++ )
      {
        for ( j = 0; j < NIBBLE_SIZE; j++ )
        {
          printf ( "%c", ((( ab ) ? b : a ) & mask ) ? '1' : '0' );
          mask >>= 1;
          }
        printf ( " " );
        }
      printf ( "%s", ( ab ) ? "\n" : " " );
      if ( ab )
      {
        b >>= 1;
        }
      else
      {
        a >>= 1;
#if defined(FIX_COMPILER_BUG)
# if (INTEGER == long)
        a |= SIGN_BIT;    /* This is a work-around for the 3b2 compiler bug. */
# endif
#endif
        }
      }
    }
  }

 /* -------------------------------cut here--------------------------------- */

  This little program might well produce some interesting surprises on
your machine in the same way it did on mine. I have an AT&T 3b2/400 and
use the K & R style compiler. Interestingly, the above program did what
I expected it to do when the integers were short, the sign bit is extended,
but when the integers are long the sign bit is NOT extended. In this case
the different behaviour is caused by the compiler always issuing a Logical
Shift instruction, when it should issue a Arithmetic Shift instruction for
signed integers and a Logical Shift instructon for unsigned ones. In the
case of the short int the varable is loaded from memory into the register
with a sign extend load instruction, this makes the Logical Shift instruction
right work correctly for short ints, but not for longs.  I had to examine
the assember codes output by the compiler in order to discover this.

  Here are the compiler invocation lines.

cc -olong.shifts -DFIX_COMPILER_BUG -DINTEGER=long shifts.c

  and

cc -oshort.shifts -DINTEGER=short shifts.c

  Experiment with the "-DFIX_COMPILER_BUG" and see what your compiler does.

  Finally: What does this little gem do?

 /* -------------------------------cut here--------------------------------- */

#include <stdio.h>

main()
{
  int a = 123,
      b = 456;

  printf ( "a = %d, b = %d\n", a, b );

  b ^= a;
  a ^= b;
  b ^= a;

  printf ( "a = %d, b = %d\n", a, b );
  }

 /* -------------------------------cut here--------------------------------- */

  And how does it do it?

  If you can't work it out in your head, get a bit of paper,
make the set of boxes to represent the numbers using binary notation.
Now put in the ones and zeros for the values of a & b for each statement.
Cut the program out of the Lesson file and compile it. 
Is the output what you thought it was going to be?

 /* -------------------------------cut here--------------------------------- */

Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
and storage in electronic data retrieval systems, but not
modification, of this course and its use for personal
study only, provided all the copyright notices are left in the 
text and are printed in full on any subsequent paper reproduction.
In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                 Lesson 5.

                      The Pre-processor and Header Files. 

The pre-processor is activated by a '#' character in column one of the source
code. There are several statements vis:

#include

#define
#undef

#if
#else
#endif

#ifdef
#ifndef

#pragma

  #include.

  In the programming examples presented in the previous lessons you will
probably have noticed that there is this statement:

#include <stdio.h>

right at the start of the program text. This statement tells the pre-processor
to include the named file in the your program text. As far as the compiler is
concerned this text appears just as if you had typed it yourself!

  This is one of the more useful facilities provided by the 'C' language.
The #include statement is frequently combined with the #if construct.
In this program fragment the file "true.h" is included in your program
if the pre-processor symbol FLAG is true, and "false.h" included if FLAG
is false.

#if ( FLAG )
# include "true.h"
#else
# include "false.h"
#endif

This mechanism has many uses, one of which is to provide
portability between all the 57,000 slightly different versions of unix and also
other operating systems. Another use is to be able to alter the way in which
your program behaves according to the preference of the user.

Of course, you will be asking the question "Where is the file stored?".
Well, if the filename is delimited by the "<" and ">" characters as in the
example above the file comes from the /usr/include directory, but if the name
of the file is delimited by quotes then the file is to be found in your current
working directory. (This is not quite the whole truth as 'C' compilers allow
you to extend the search path for the include files using command line option
switches. - See your compiler manual for the whole story. )

So, I would like to suggest that you to have a look around the /usr/include
directory and its /sys sub-directory. You should use either your editor
in 'view' mode or the pg utility. This will ensure that you can't have an
accident and alter one of the files by mistake if you are slightly silly
and just happen to be logged on as the super-user.

A typical file to examine is usr/include/time.h.

It's quite small so here it is.

/*  Copyright (c) 1984 AT&T  */
/*    All Rights Reserved    */

/*  THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T  */
/*  The copyright notice above does not evidence any     */
/*  actual or intended publication of such source code.  */

#ident  "@(#)/usr/include/time.h.sl 1.5 4.2 04/20/87 18195 AT&T-SF"
/*  3.0 SID #  1.2  */
struct  tm {  /* see ctime(3) */
  int  tm_sec;
  int  tm_min;
  int  tm_hour;
  int  tm_mday;
  int  tm_mon;
  int  tm_year;
  int  tm_wday;
  int  tm_yday;
  int  tm_isdst;
};
extern struct tm *gmtime(), *localtime();
extern char *ctime(), *asctime();
int  cftime(),  ascftime();
extern void tzset();
extern long timezone, altzone;
extern int daylight;
extern char *tzname[];

  As you can see ( forgetting about the comments and #ident ) there are three
different uses for the file.

  a) The definition of data structures and types.
  b) The declaration of functions which use the data structures.
  c) The declaration of of external data objects.

  These lines of code are all you need in your program in order to be able to
use, in this case, the library routine to access the clock in the computer,
but of course the paradigm applies to all programs which are created by one
programmer and used by another member of the programming team.  Note that, by
proxy, or whatever, the author of the library routines has in effect become
a member of your programming team.

  You might care to write a program or two which use this header file,
and for those who are motivated it might be an idea to re-implement localtime
so that it understands Summer Time in the Southern Hemisphere. (!)

Using another totally trivial example in order to get the idea across please
examine the hello world program printed immediately below.

/* ------------------------------------------------------------ */

#ident "@(#) hw_uc.h UPPER CASE version."

#define HELLO_MESSAGE "HELLO WORLD...\n";

/* ------------------------------------------------------------ */

#ident "@(#) Hello World"

#include <stdio.h>
#include HW_H

#if !defined( HELLO_MESSAGE )
# error "You have forgotten to define the header file name."
#endif

char *format = "%s", 
     *hello = HELLO_MESSAGE;

main()
{
  printf ( format, hello );
  }

/* ------------------------------------------------------------ */

You will no doubt notice that the symbol HW_H is used instead of a header file
name. This gives us the ability to force the inclusion of any file we wish by
defining the symbol HW_H to be the desired file name. It can be done like this:

cc -DHW_H="\"hw_uc.h\"" hello.c

The compiler output is placed, by default, in the file a.out, so to execute it
issue the command:

a.out

Which, fairly obviously, produces the output:

HELLO WORLD...

As we are going to generate another version of the program we had better move
the executable image file to another file name:

mv a.out hello_uc

Now to produce the other version issue the command line:

cc -DHW_H="\"hw_lc.h\"" hello.c; mv a.out hello_lc; hello_lc

Which compiles the other version of the hello.c program, using this version of
the include file:

/* ------------------------------------------------------------ */
#ident "@(#) hw_lc.h Lower Case version."

#define HELLO_MESSAGE "Hello World...\n";
/* ------------------------------------------------------------ */

and then moves the executable image to a different file and executes it.
Note that more than one command per line can be issued to the shell by
separating the commands with the ';' delimiting character.
Here - Surprise, Surprise - is the output of the second version.

Hello World...

I'd like to suggest that you use your editor to cut these example programs
and the shell file below out of the mail file and have a play with them.

/* ----------------------------------------- */

# @(#) Shell file to do the compilations.

cc -o hello_uc -DHW_H="\"hw_uc.h\"" hello.c
cc -o hello_lc -DHW_H="\"hw_lc.h\"" hello.c

/* ----------------------------------------- */


#define

  This statement allows you to set up macro definitions. The word immediately
after the #define, together with its arguments, is expanded in the program
text to the whole of the rest of the line.

#define min(a, b) ((a<b) ? a : b )

  Some things to note:

  1) There isn't a space between the last character of the symbol being defined
     and the opening parenthesis enclosing the arguments, and there MUST NOT BE
     one.

  2) The code into which the macro is expanded MUST always be enclosed in
     parentheses and for safety always use parentheses to get the arithmetic
     right.

  3) Never EVER define a macro, and use it with a side effect. e.g.

  c = min ( a++, b);                              /* DON'T _EVER_ DO THIS!!! */

     Do you think that the value of 'a' will get advanced after the
     macro is used? Well it WON'T. It gets incremented after the less
     than test and before the values get assigned! I have written a tiny
     program which uses the min macro above. Have a look at the output
     from the pre-processor. Lesson One told you how to do this.
     Now execute it and get an educative surprise!

/* ----------------------------------------- */

#include <stdio.h>
#define min(a, b) ((a<b) ? a : b )

main()
{ int a,b,c;

  a = 1;
  b = 2;
  c = min ( a++, b);                              /* DON'T _EVER_ DO THIS!!! */
  printf ( "a: %d, b: %d, c: %d\n", a, b, c );
  }

/* ----------------------------------------- */

  4) You can continue a macro on the next line by putting a \ ( back-slash )
     as THE VERY LAST character on the line. NOTHING, not even a space may
     follow, as your compiler just can't handle it. I spent far too long trying
     to find one of those really difficult bugs, and it turned out that this
     was the problem - spaces are transparent aren't they?

  5) Using macros is fast and convenient, but they do take up a lot of memory
     because the code is expanded and inserted into the output stream for
     every occurrence of the macro in your code. There is a trade-off between
     using a macro and a function.

  The symbol does not have to be the handle for a macro expansion, but can just
be equated to a single constant. This is done many times over in the header
files provided by the operating system vendor. Have a look in
/usr/include/sys/file.h for an example of this.

#undef

  Not surprisingly this preprocessor command removes a symbol WHICH IS BEING
  USED BY THE PRE-PROCESSOR - don't confuse it with compiler proper symbols.

  Note that the symbol can be a macro name, in which case the space
  used for the code expansion is made available for re-use.

#if ( FLAG )

      /* Code in here is sent on to the compiler if FLAG is true. */

#else

      /* Code in here is sent on to the compiler if FLAG is false. */

#endif

  When the pre-processor encounters one of these, the lines of code between the
#if and the corresponding #else or #endif are either skipped over or allowed to
proceed to the compilation phase depending on the truth or falsity of the
logical expression ( FLAG ). All the logical and boolean expressions available
as part of the 'C' language are available here. You are also allowed to say:

#if defined( FLAG )  or,
#if !defined( FLAG )


  The symbol FLAG may be an expression which reduces to a boolean value.

  A convention which is adhered to quite well is that all pre-processor
symbols are in UPPER_CASE so as to make them obvious.

#ifdef FLAG  or,
#ifndef FLAG

  These two statements are the old fashioned way of testing whether a symbol is
defined or not. They are absolutely the same as the previous example.

  There are two more pre-processor statements, namely the #pragma and 
the "stringizing" operator. The #pragma is used to alter the way in which 
the compiler works on a block of code, but it is completely implementation
dependant and you must refer to your compiler manual. I can't help as 
they are all different. The "stringizing" operator is quite an advanced
technique and will be dealt with later on.


Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                  Lesson 6

          Libraries, why we have them, and how to make and use them.

  In order to simplify the creation of programs for its customers,
software vendors make available one or more libraries of functions
which have general application.

  When you write a program in 'C' much, if not most, of the "hard work"
is done by explicitly called functions, which you call by simply writing
their names in your program script. Unfortunately there is a little bit
more to it than that. If the function returns a value which is other
than of type int, you have to tell the compiler the type of the returned
value. An example will, I hope, make things clear.

  Let's suppose that you have been given the task of, making things
stupidly simplistic for a book example, sorting a list of names into
alphabetical order. ( Yes, I do know this can, and should be done in just 
one line of shell script! However that's another story for another day. )

  You look diligently through the Programmer Reference Manual, discover
that the prose is almost opaque, and find a couple of interesting looking
routines called strcmp, and qsort. You decide to use these library
functions. Now for just a moment lets consider the ins and outs of what,
in effect, you have just done. You have just asked a member of the team
of programmers who created the library to join you, by proxy as it were,
in creating your masterpiece. A useful concept, which has been in use
almost since the start of electronic computing.

  To re-focus the mind on the task at hand; let's look in the Reference Manual
at the page for qsort(3C) - The 3C in parenthesis is the cryptic code which
is the unix apology for a reference to section 3C in the Manual! So find 
section 3C and look up qsort. Now have a look at the SYNOPSIS, and notice
that there is no mention of a header file to #include, and also notice that
qsort returns a void, not an int. This means that there is no header file
/usr/include/qsort.h ( for my version of unix - system V Release 3.2.2 -
anyway ) and you have to declare qsort yourself as an external function. 
Also turn to the page string(3C) in the fine manual. Notice that the 
SYNOPSIS here includes the line #include <string.h> so you have to put 
it in your program text. Once more an example to make it all clear.

 /* -------------------------------cut here--------------------------------- */

#ident "qsort-demo2.c - sorting an array of pointers."
#if defined(__STDC__)
#include <stdlib.h>          /* includes - sys/stdtypes.h    */
#endif

#include <ctype.h>           /* includes - nothing           */
#include <stdio.h>           /* includes - nothing           */
#include <string.h>          /* includes - sys/stdtypes.h    */
#include <assert.h>          /* includes - nothing           */

/*
** This makes an array of pointers to the strings.
** The is no need to dimension the array as the compiler does it for us
** by counting up the number of initialisers.
*/

static char *namev[] =
{
   "John Nagle", "Colin Douthwaite", "Ian Lance Taylor", "Brian J. Murrell",
   "Pete", "Geoff Mccaughan", "David Liebert", "Bill Baucum",
   "Victor Volkman", "Chay R Harley", "Dan Romanchik", "Larry Kollar",
   "Gaston Ormazabal", "Arijit Chandra", "Kenneth Mark Hopkinson",
   "Kerr Hatrick", "Tim Love", "Robert M. Juranitch", "Jeffrey Micke",
   "Duong Quoc", "Jagadesh Vasudevamurthy"
};

#define NUMBER_OF_NAMES (sizeof(namev)/sizeof(namev[0]))

/*
** Wrapper function to use the library string comparison function.
** The pointers to pointers which point at the character strings are
** dereferenced to be pointers to the strings themselves.
*/

int CompareStrings (s1, s2)
char **s1, **s2;
{
   return strcmp(*s1, *s2);
}

/*
** Wrapper function to sort by the last name instead of the whole string.
** The pointer to pointer (etc) issue is the same as the above.
** This shows how a more complicated comparison is possible, because
** the comparison function is called from within the qsort function.
*/

int CompareNames ( s1, s2 )
char **s1, **s2;
{
   char *last1, *last2;

   if ((last1 = strrchr(*s1, ' ')) == NULL)
      last1 = *s1;
   else
      last1++;
   
   if ((last2 = strrchr(*s2, ' ')) == NULL)
      last2 = *s2;
   else
      last2++;
   
   return strcmp(last1, last2);
}

void wait_for_key()
{
   char ch;

   printf ( "Press the RETURN key to continue." );
   fflush ( stdout );
   ( void ) getchar ();
   }

int main ()
{
   int i;

   fprintf(stdout, "\n\nOriginal:\n\n");
   for (i=0; i<NUMBER_OF_NAMES; i++) fprintf(stdout, "%s\n", namev[i]);

   wait_for_key();

   qsort((char *)namev, NUMBER_OF_NAMES, sizeof(char *), CompareStrings);
   fprintf(stdout, "\n\nSorted:\n\n");
   for (i=0; i<NUMBER_OF_NAMES; i++) fprintf(stdout, "%s\n", namev[i]);

   wait_for_key();

   qsort((char *)namev, NUMBER_OF_NAMES, sizeof(char *), CompareNames);
   fprintf(stdout, "\n\nSorted by last name:\n\n");
   for (i=0; i<NUMBER_OF_NAMES; i++) fprintf(stdout, "%s\n", namev[i]);

   return 0;
}

 /* -------------------------------cut here--------------------------------- */

  Note very well:-
  
  I wanted 21 short character strings for the data items for the demo
to sort. So grep, uniq, cut, tail, and finally a tiny bit of vi fished
eminently suitable strings out of "mail.received". If your name is not
on the list, well I'm sorry, but the world is not a fair place!

  So that's how you use library routines. I chose qsort because it is
simple to use, and shows off a feature of 'C' well, that's the ability
to use a name of a function as a pointer and then execute that function
from within the called function. It's strcmp in this case. A quick look
at the compiler output is instructive.

  There is just one more point to notice about using function libraries.
The 'C' compilation system will load functions from the library /lib/libc.a
as a default. All others have to be indicated to the linking loader by a
switch on the shell interactive command line. 

$ cc -o prog prog.c -L /usr/local/lib -lgdbm -lmalloc

  You might use this command line to compile and link a program which
uses both the GNU gdbm data-base manager library, which is installed in
the directory /usr/local/lib, and the enhanced malloc library. Now, there
hangs a tale! I remember having to compile a program suit off Usenet and
it just would not work properly. No error messages, no warnings, no
missing linking-loader symbols. It just "died" when I tried to run it.
After many, many hours of total frustration, I thought that I would try
linking in the enhanced malloc library. Presto! It worked.


  Note very well.

  A common misconception is the notion that having a #include <whatever.h>
line in the source text will automagically tell the linking loader to
get the functions from the appropriate library. Remove this erroneous
notion from your mind. It won't. The -lwhatever flag on the shell command
line which initiates execution of "cc" or "ld" is the only way to tell the
loader where to look for the required library.

  Acknowledgement:- 

  mundrane@jove.rutgers.edu (Michael Mundrane)

  has provided the enhanced version of the demonstration program.


Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                     Lesson 7.

                             De-bugging Strategies.

      >>>>>>>> Proper Preparation Prevents Piss-Poor Performance. <<<<<<<<

  This lesson is really a essay about how to go about writing programs.

  I know that by far the best way to greatly reduce the amount of effort
required to get a program going properly is to avoid making mistakes in the
first palace! Now this might seem to be stating the absolute obvious, and it
is but after looking at many programs it would seem that there is a very
definite need to say it.

  So how does one go about reducing the probability of making mistakes?

  There are many strategies, and over the years I have evolved my own set.
  I have found that some of the most important are:

  1) Document what you are going to do before yes BEFORE you write any code.
     Set up the source files for the section of the program you are going to
     write and put some lines of explanation as to what you intend to do in
     this file. Be as precise as you can, but don't go into the detail of
     explaining in English, or your First Language, exactly what every
     statement does.

  2) Make sure that you keep each file as small as is sensible. Some program
     authors say that one should put only one function in a file. It's my
     personal opinion that this is going a little bit over the top, but
     certainly you should not have more than one logical activity in a source
     file. It's easier to find a needle in a tiny haystack than in a big one!

  3) Always use names for the objects in your program which are fully
     descriptive, or at the very least are meaningful nmemonics. Put yourself
     in the position of some poor soul who - a couple of years later, after you
     have long finished with the project, and left the country - has been given
     the task of adding a small feature to your exquisite program. Now in the
     rush to get your masterpiece finished you decided to use variable names
     like "a4" and "isb51" simply so that you can get the line typed a
     fraction of a second faster than if you used something like
     "customer_address[POST_CODE]" and "input_status_block[LOW_FUEL_TANK_#3].
     The difference in ease of understanding is obvious, isn't it? However
     judging by some programs which I have seen published in both magazines and
     in the public domain program sources, the point has still to be made.

  4) ALWAYS take great care with the layout of your code.
     It's my opinion that the opening brace of ALL program structures should
     be on a new line. Also if you put them in the leftmost column for structs,
     enums, and initialised tables, as well as functions, then the
     'find function' keystrokes ( "[[" and "]]" ) in vi will find them as well
     as the functions themselves. Make sure you have the "showmatch" facility
     in vi turned on. ( And watch the cursor jump when you enter the 
     right hand brace, bracket, or parenthesis. )
  
  5) Try as hard as you can to have as few global variables as possible.
     Some people say never have any globals. This is perhaps a bit too
     severe but global variables are a clearly documented source of
     programming errors. If it's impossible to perform a logical activity
     in an efficient way without having a global or two, then confine
     the scope of the globals to just the one file by marking the defining
     declaration "static". This stops the compiler producing a symbol which
     the linking loader will make available to all the files in your source.

  6) Never EVER put 'magic numbers' in you source code. Always define constants
     in a header file with #define lines or enum statements.

     Here is an example:-


/* ----------------------------------------- */

#include <stdio.h>

enum status_input_names
{
  radiator_temperature,
  oil_temperature,
  fuel_pressure,
  energy_output,
  revolutions_per_minute
  };

char *stats[] =
{
  "radiator_temperature",
  "oil_temperature",
  "fuel_pressure",
  "energy_output",
  "revolutions_per_minute"
  };

#define NUMBER_OF_INPUTS ( sizeof ( stats ) / sizeof ( stats[0]))

main()
{
  enum status_input_names name;

  printf ( "Number of Inputs is: %d\n", NUMBER_OF_INPUTS );
  for ( name = radiator_temperature; name < NUMBER_OF_INPUTS; name++)
  {
    printf ( "\n%s", stats[ name ] );
    }
  printf ( "\n\n" );
  }

/* ----------------------------------------- */

  Note that as a side effect we have available the meaningful symbols
  radiator_temperature etc. as indices into the array of status input names
  and the symbol NUMBER_OF_INPUTS available for use as a terminator in the
  'for' loop. This is quite legal because sizeof is a pseudo-function and the
  value is evaluated at the time of compilation and not when the program is
  executed. This means that the result of the division in the macro is
  calculated at the time of compilation and this result is used as a literal
  in the 'for' loop. No division takes place each time the loop is executed.

  To illustrate the point I would like to tell you a little story which is
  fictitious, but which has a ring of truth about it.
  Your employer has just landed what seems to be a lucrative contract with
  an inventor of a completely new type of engine. We are assured that after
  initial proving trials one of the larger Japanese motor manufactures is
  going to come across with umpteen millions to complete the development of
  the design. You are told to write a program which has to be a simple and
  straightforward exercise in order to do the job as cheaply as possible.
  Now, the customer - a some-what impulsive type - realises that his
  engine is not being monitored closely enough when it starts to rapidly
  dis-assemble itself under high speed and heavy load. You have to add a
  few extra parameters to the monitoring program by yesterday morning!
  You just add the extra parameters into the enumand the array of pointers
  to the character strings. So:

enum status_input_names
{ radiator_temperature,
  radiator_pressure,
  fuel_temperature,
  fuel_pressure,
  oil_temperature,
  oil_pressure,
  exhaust_manifold_temperature
  };

  Let's continue the story about the Japanese purchase. Mr. Honda ( jun ) has
  come across with the money and the result is that you are now a team leader
  in the software section of Honda Software ( YourCountry ) Ltd. The project of
  which you are now leader is to completely rewrite your monitoring program and
  add a whole lot of extra channels as well as to make the printouts much more
  readable so that your cheap, cheerful, and aesthetic-free program can be sold
  as the "Ultimate Engine Monitoring Package" from the now world famous Honda
  Real-time Software Systems. You set to work, Honda et. al. imagine that there
  is going to be a complete redesign of the software at a cost of many million
  Yen. You being an ingenious type have written the code so that it is easy to
  enhance.

  The new features required are that the printouts have to be printed with the
  units of measure appended to the values which have to scaled and processed so
  that the number printed is a real physical value instead of the previous
  arrangement where the raw transducer output was just dumped onto a screen.

  What do you have to do?

  Thinking along the line of "Get the Data arranged correctly first".
  You take you old code and expand it so that all the items of information
  required for each channel are collected into a struct.

enum status_input_names
{
  radiator_temperature,
  radiator_pressure,
  fuel_temperature,
  fuel_pressure,
  oil_temperature,
  oil_pressure,
  exhaust_manifold_temperature,
  power_output,
  torque
  };

typedef struct channel
{
  char *name;                    /* Channel Name to be displayed on screen. */
  int nx;                        /* position of name on screen x co-ordinate. */
  int ny;                        /* ditto for y */
  int unit_of_measure;           /* index into units of measure array */
  char value;                    /* raw datum value from 8 bit ADC */
  char lower_limit;              /* For alarms. */
  char upper_limit;
  float processed_value;         /* The number to go on screen. */
  float offset;
  float scale_factor;
  int vx;                        /* Position of value on screen. */
  int vy;
  }CHANNEL;

enum units_of_measure { kPa, degC, kW, rpm, Volts, Amps, Newtons };

char *units { "kPa", "degC", "kW", "rpm", "Volts", "Amps", "Newtons" };

CHANNEL data [] =
{
  { "radiator temperature",
  { "radiator pressure",
  { "fuel temperature",
  { "fuel pressure",
  { "oil temperature",
  { "oil pressure",
  { "exhaust manifold temperature",
  { "power output",
  { "torque",
  };

#define NUMBER_OF_INPUTS sizeof (data ) / sizeof ( data[0] )

Now the lesson preparation is to find the single little bug in the above
program fragment, to finish the initialisation of the data array of type
CHANNEL and to have a bit of a crack at creating a screen layout
program to display its contents. Hint: Use printf();
( Leave all the values which originate from the real world as zero. )


  Here are some more tips for young players.

  1) Don't get confused between the logical equality operator,

     ==

     and the assignment to a variable operator.

     =

     This is probably the most frequent mistake made by 'C' beginners, and
     has the great disadvantage that, under most circumstances, the compiler
     will quite happily accept your mistake.

     You can make the compiler hiccup on this error if you put the 
     constant on the left hand side and the variable or function call
     on the right, e.g:

     if ( TOP_BOUND = i ) { ... }; /* TOP_BOUND is #defined somewhere */
                    ^
                    ERROR

  2) Make sure that you are aware of the difference between the logical
     and bit operators.

     &&         This is the logical AND function.
     ||         This is the logical OR function.
                The result is ALWAYS either a 0 or a 1.

     &          This is the bitwise AND function used for masks etc.
                The result is expressed in all the bits of the word.

     |          This is the bitwise OR function used for masks etc.
                The result is expressed in all the bits of the word.

  3) Similarly to 2 be aware of the difference between the logical
     complementation and the bitwise one's complement operators.

     !          This is the logical NOT operator.
     ~          This is the bitwise ones complement op.

     Some further explanation is required. In deference to machine efficiency a
     LOGICAL variable is said to be true when it is non-zero. So let's set a
     variable to be TRUE.

     00000000000000000000000000000001  A word representing TRUE.
                                       Now let's do a logical NOT  !.
     00000000000000000000000000000000  There is a all zero word, a FALSE.

     00000000000000000000000000000001  That word again. TRUE.
                                       Now for a bitwise complement  ~.
     11111111111111111111111111111110  Now look we've got a word which is
                                       non-zero, still TRUE.

                                       Is this what you intended?
   
  4) It is very easy to fall into the hole of getting the
     '{' & '}'; '[' & ']'; '(' & ')'; symbol pairs all messed up and the
     computer thinks that the block structure is quite different from that
     which you intend. Make sure that you use an editor which tells you the
     matching symbol. The UNIX editor vi does this provided that you turn
     on the option. Also take great care with your layout so that the block
     structure is absolutely obvious, and whatever style you choose do take
     care to stick by it throughout the whole of the project.
     A personal layout paradigm is like this:

  Example 1.

function_type function_name ( a, b )
type a;
type b;
{
  type variable_one, variable_two;

  if ( logical_expression )
  {
    variable_one = A_DEFINED_CONSTANT;
    if ( !return_value = some_function_or_other ( a,
                                                  variable_one,
                                                  &variable_two
                                                  )
         )
    {
      error ( "function_name" );
      exit ( FAILURE );
      }
    else
    {
      return ( return_value + variable_two );
      } 
    }    /* End of "if ( logical_expression )" block */
  }    /* End of function */

  I would agree that this layout could be described as somewhat
  idiosyncratic, but it is easy to do using vi with this initialisation
  script in either the environment variable EXINIT or the file
  ${HOME}/.exrc:-

set showmode autoindent autowrite tabstop=2 shiftwidth=2 showmatch wm=1

  Example 2.

void printUandG()
{
  char *format =
"\n\
           User is: %s\n\
          Group is: %s\n\n\
 Effective User is: %s\n\
Effective Group is: %s\n\n";

  ( void ) fprintf ( tty,
                     format,
                     passwd_p->pw_name,
                     group_p->gr_name,
                     epasswd_p->pw_name,
                     egroup_p->gr_name
                     );
  }

  Notice how it is possible to split up format statements with a '\' as
  the last character on the line, and that it is convenient to arrange
  for a nice output format without having to count the
  field widths. Note however that when using this technique that the '\'
  character MUST be the VERY LAST one on the line. Not even a space may
  follow it!

  In summary I *ALWAYS* put the opening brace on a new line, set the tabs
  so that the indentation is just two spaces, ( use more and you very quickly
  run out of "line", especially on an eighty column screen ). If a statement
  is too long to fit on a line I break the line up with the arguments set out
  one to a line and I then the indentation rule to the parentheses "()" 
  as well. Sample immediately above. Probably as a hang-over from a particular
  pretty printing program which reset the indentation position after the
  printing of the closing brace "}", I am in the habit of doing it as well.
  Long "if" and "for" statements get broken up in the same way. This is
  an example of it all. The fragment of code is taken from a curses oriented
  data input function.

  /*
  ** Put all the cursor positions to zero.
  */

  for ( i = 0;
        s[i].element_name != ( char *) NULL &&
        s[i].element_value != ( char *) NULL;
        i = ( s[i].dependent_function == NULL )
            ? s[i].next : s[i].dependent_next
        )
  {                              /* Note that it is the brace and NOT the    */
                                 /* "for" which moves the indentation level. */
    s[i].cursor_position = 0;
    }
 
  /*
  ** Go to start of list and hop over any constants.
  */

    for ( i = edit_mode = current_element = 0;
          s[i].element_value == ( char *) NULL ;
          current_element = i = s[i].next
          ) continue;                               /* Note EMPTY statement. */

  /*
  ** Loop through the elements, stopping at end of table marker,
  ** which is an element with neither a pointer to an element_name nor
  ** one to a element_value.
  */

  while ( s[i].element_name != ( char *) NULL &&
          s[i].element_value != ( char *) NULL
          )
  {
    int c;           /* Variable which holds the character from the keyboard. */

    /*
    **  Et Cetera for many lines.
    */

    }

  Note the commenting style. The left-hand comments provide a general
overview of what is happening and the right-hand ones a more detailed view.
The double stars make a good marker so it is easy to separate the code and
the comments at a glance.

  The null statement.

  You should be aware that the ";" on its own is translated by the compiler
as a no-operation statement. The usefulness of this is that you can do
little things, such as counting up a list of objects, or positioning a pointer
entirely within a "for" or "while" statement. ( See example above ).
There is, as always, a flip side. It is HORRIBLY EASY to put a ";" at the
end of the line after the closing right parenthesis - after all you do just
that for function calls! The suggestion is to both mark deliberate null
statements with a comment and to use the statement "continue;". Using
the assert macro will pick up these errors at run time.

  The assert macro.

  Refer to the Programmers Reference Manual section 3X and find the
documentation on this most useful tool.

  As usual an example is by far the best way to explain it.

/* ----------------------------------------- */

#ident "@(#) assert-demo.c"

#include <stdio.h>
#include <assert.h>

#define TOP_ROW 10
#define TOP_COL 10

main()
{
  int row, col;

  for ( row = 1; row <= TOP_ROW; row++);
  {
    assert ( row <= TOP_ROW );
    for ( col = 1; col <= TOP_COL; col++ )
    {
      assert ( col <= TOP_COL );
      printf ( "%4d", row * col );
      }
    printf ( "\n" );
    }
  }

/* ----------------------------------------- */

  Which produces the output:-

Assertion failed:  row <= TOP_ROW , file assert-demo.c, line 15
ABORT instruction (core dumped)

  It does this because the variable "row" is incremented
to one greater than The value of TOP_ROW.

  Note two things:

  1) The sense of the logical condition. The assert is asserted
     as soon as the result of the logical condition is FALSE.
     Have a look at the file /usr/include/assert.
     Where is the ";" being used as an empty program statement?

  2) The unix operating system has dumped out an image of the executing
     program for examination using a symbolic debugger. Have a play with
     "sdb" in preparation for the lesson which deals with it in more
     detail.

  Let's remove the errant semi-colon, re-compile and re-run the program.

  Here's the output:-

                     1   2   3   4   5   6   7   8   9  10
                     2   4   6   8  10  12  14  16  18  20
                     3   6   9  12  15  18  21  24  27  30
                     4   8  12  16  20  24  28  32  36  40
                     5  10  15  20  25  30  35  40  45  50
                     6  12  18  24  30  36  42  48  54  60
                     7  14  21  28  35  42  49  56  63  70
                     8  16  24  32  40  48  56  64  72  80
                     9  18  27  36  45  54  63  72  81  90
                    10  20  30  40  50  60  70  80  90 100

                   The ten times multiplication table, for you
                 to give to the nearest primary-school child!

  I would agree that it is not possible to compare the value of a program
layout with a real work of fine art such as a John Constable painting or
a Michaelangelo statue, but I do think a well laid out and literate example
of programming is not only much easier to read and understand, but also it
does have a certain aesthetic appeal.


Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


                                 Lesson 8.

  This lesson will examine how to use the program structure - as opposed
to data structure - reserved words.

  Lets start with the looping structures:

  do repeated_statement while ( logical_expression );

  repeated_statement, which may be a block of code, will be executed
repetitively until the logical_expression, becomes false. If you have been
exposed to ( corrupted by? ) another language remember that there is no
`until' test at the end of a loop. Note that the repeated_statement is always
executed once irrespective of the state of the logical_expression.

  while ( logical_expression ) repeated_statement;

  repeated_statement is executed repetitively while the logical_expression
is true. Once again statement may be a block of code. Note that if the 
logical_expression evaluates to FALSE then the repeated_statement is NEVER
executed.

  Associated with the looping structures are the control words:

  break;
  continue;

  break; allows you to leave a loop in the middle of a block, and
  continue; allows you to re-start it from the top.

  Finally we must not forget the most common and useful looping construct:

  for ( initialising statement; logical_expression; incremental_statement )
  repeated_statement;

  Some further explanation is needed. The initialising statement is
executed once, but to allow for the need to initialise several separate
variables the assignment statements may be separated by commas. The
logical_expression must be true for the loop to run, and the
incremental_statement is executed once each time the loop is run.
The for statement is completely general and may, for example, be used to
manipulate a set of pointers to operate on a linked list.

Some examples.

  A do loop program.

#ident "@(#) do_demo.c - An example of the do loop"

#include <stdio.h>

main()
{
  char character;

  character = 'a';

  do printf ( "%c", character ); while ( character++ < 'z' );
  printf ( "\n" );
  }

  Fairly obviously it prints:

abcdefghijklmnopqrstuvwxyz

  A while loop example.

#ident "@(#) while_demo.c - An example of the while loop"

#include <stdio.h>

main()
{
  char character;

  character = 'a';

  while ( character <= 'z' ) printf ( "%c", character++ );
  printf ( "\n" );
  }

  Its output is exactly the same as the previous example:

abcdefghijklmnopqrstuvwxyz

  In this totally trivial case it is irrelevant which program structure
  you use, however you should note that in the `do' program structure the
  repeated statement is always executed at least once.
  A for loop example.

  The `for' looping structure.

#ident "@(#) for_demo.c - An example of the for loop"

#include <stdio.h>

main()
{
  char character;

  for ( character = 'a'; character <= 'z' ; character++ )
  {
    printf ( "%c", character );
    }
  printf ( "\n" );
  }

  Surprise, Surprise!

abcdefghijklmnopqrstuvwxyz

  You should be aware that in all the looping program structures, the
repeated statement can be a null statement ( either just a `;' or the
reserved word `continue;' ). This means that it is possible to - for
example - position a pointer, or count up some items of something or other.
It isn't particularly easy to think up a trivial little program which
demonstrates this concept, however the two `for' loops give some indication
of the idea.

#ident "@(#) pointer_demo.c - Pointer operations with the for loop"

#include <stdio.h>

main()
{
  char character, *character_pointer, alphabets [ 53 ];

  for ( character = 'a', character_pointer = alphabets;  /* Start conditions */
        character <= 'z';                                /* Run while true   */
        *character_pointer++ = character++               /* All the work     */
        ) continue;

  for ( character = 'A';  /* character_pointer is at the right place already */
        character <= 'Z';
        *character_pointer++ = character++
        ) continue;

  *character_pointer = (char) '\000'; /* NULL character to terminate string. */

  printf ( "%s\n\n", alphabets );
  }

  Another Surprise!

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

  So much for the looping structures provided by the `C' language. The
other main structures required to program a computer are the ones which
alter the program flow. These are the switch, and the if and its extension
the if ... else combination. More demo programs are much the best way of
getting the message across to you, so here they are, first the if construct.

#ident "if_demo.c"

#include <stdio.h>

main(argc, argv)
int argc;
char **argv;
{
  if ( argc > 1 ) printf ( "You have initiated execution with arguments."};
  }

  And the if ... else demo.

#ident "if_else_demo.c"
/*
** The Language #define could go in the compiler invocation line if desired.
*/

#define ENGLISH 

#include <stdio.h>

/*
** The message and text fragments output by the program.
*/

char *messages[] =
{
#if defined( ENGLISH )
#ident "@(#)ENGLISH Version"
  "\nUsage: if_else_demo <numeric argument 1> <numeric argument 2>\n\n",
  "The first argument is ",
  "the second",
  "equal to ",
  "bigger than ",
  "smaller than "
#endif

#if defined( FRANCAIS )
#ident "@(#)FRENCH Version"

  put the French translation in here so that we are ready to export to
  French speaking Countries. I'd be grateful if a French speaker could
  make the translation for me.

#endif
  };

/*
** Meaningful words defined to constants
*/

#define USAGE 0
#define FIRST 1
#define SECOND 2
#define EQUAL 3
#define BIGGER 4
#define SMALLER 5

#define SUCCESS 0
#define FAILURE 1

/*
** We need this more than once so it can be put in a function.
*/

void usage()
{
  printf ( messages[USAGE]);
  exit ( FAILURE );
  }
     
/*
** Main program function starts here. ( At the top of a page no less! )
*/

int main ( argc, argv )
int argc;
char **argv;
{
  int message_index;
  double i, j, strtod();
  char *ptr;

  if ( argc != 3 ) usage();                  /* have we been given the right */
                                             /* number of arguments. */
  i = strtod ( argv[1], &ptr);               /* Convert to a double float. */
  if ( ptr == argv[1] ) usage();             /* Successful conversion? */
  j = strtod ( argv[2], &ptr);               /* Convert to a double float. */
  if ( ptr == argv[2] ) usage();             /* Successful conversion? */

/*
** This statement uses the "ternary conditional assignment" language 
** construction to assign the value required to the message indexing variable.
** Note that this concept is efficient in both the generation of machine code
** output ( compile the program with a -S switch and have a look ) and in the
** ease with which it can be understood. The assignment is obvious instead of
** being buried under a litter of `if' and `else' keywords.
*/

  message_index = ( i == j ) ? EQUAL : ( i > j ) ? BIGGER : SMALLER;

/*
** Now print the message.
*/

  (void) printf ( "\n%s%s%s\n\n",     /* Format string specifying 3 strings. */
                  messages[ FIRST ],                   /* Address of string. */
                  messages[ message_index ],           /*        ditto.      */
                  messages[ SECOND ]                   /*        ditto.      */
                  );
  return ( SUCCESS );
  }

  Well as you can no doubt gather it simply compares two numbers on the 
command line and ejects a little message depending on the relative magnitude
of the numbers. In the UNIX tradition the help message is perhaps somewhat
terse, but it serves the purpose of getting you - the student - to think
about the importance of creating programs which always cope with nonsensical
input in a civilised way. Here are the lines of output.

Usage: if_else_demo <numeric argument 1> <numeric argument 2>

The first argument is equal to the second

The first argument is smaller than the second

The first argument is bigger than the second

  Now that the international community is shrinking with vastly improved
telecommunications, it is perhaps a good idea to think carefully about 
creating programs which can talk in many languages to the users. The method
of choice is - I believe - that presented above. The #if defined( LANGUAGE )
gives us an easy method of changing the source code to suit the new sales
area. Another possibility is to put all the text output needed from a program
into a file. The file would have to have a defined layout and some consistent
way of `getting at' the message strings.

  From a commercial point of view this may or may not be a good business plan.
Quite definitely it is an absolute no no to scatter a mass of string literals
containing the messages and message fragments all over your program script.

  There are two more methods of altering the program flow.

  1 ) The goto a label.
  2 ) The setjump / longjmp library routines.

  The concept of the go to a label construction has had reams of literary
verbiage written about it and this author does not intend to add to the pile.
Suffice it to say that a goto is a necessary language construct. There are a
few situations which require the language to have ( in practice ) some form of
unconditional jump. Treat this statement with great caution if you wish your
code to be readable by others. An example of legitimate use.

  for ( a = 0; a < MATRIX_SIZE; a++ )
  {
    for ( b = 0; b < MATRIX_SIZE; b++ )
    {
      if ( process ( matrix, a, b )) goto bad_matrix;
      }
    }
  return ( OK );

bad_matrix:

   perror ( progname, "The data in the matrix seems to have been corrupted" );
   return ( BAD );

  This is one of the very few "legitimate" uses of goto, as there is no
"break_to_outer_loop" in `C'. Note that some compilers complain if the label
is not immediately followed by a statement. If your compiler is one of these
naughty ones, you can put either a `;' or a pair of braces `{}' after the
`:' as a null statement.

  An example of a program package which makes extensive use of the goto is the
rz and sz modem communications protocol implementation by Chuck Forsberg of
Omen Technology. You should download it and study the code, but do remember
that the proof of the pudding argument must apply as the rz & sz system has
become extremely popular in its application because it works so well.

  The other method of changing program flow is the setjump and longjmp pair of
library functions. The idea is to provide a method of recovery from errors
which might be detected anywhere within a large program - perhaps a compiler,
interpreter or large data acquisition system. Here is the trivial example:

#ident "set_jmp_demo.c"

#include <stdio.h>
#include <setjmp.h>

jmp_buf save;

main()
{
  char c;

  for ( ;; )                     /* This is how you set up a continuous loop. */
  {
    switch ( setjmp( save ))
    {
case 0:
      printf ( "We get a zero returned from setjmp on setup.\n\n");
      break;                   /* This is the result from setting up. */

case 1:
      printf ( "NORMAL PROGRAM OPERATION\n\n" );
      break;

case 2:
      printf ( "WARNING\n\n" );
      break;

case 3:
      printf ( "FATAL ERROR PROGRAM TERMINATED\n\nReally Terminate? y/n: " );
      fflush ( stdout );
      scanf ( "%1s", &c );
      c = tolower ( c );
      if ( c == 'y' ) return ( 1 );
      printf ( "\n" );
      break;

default:
      printf ( "Should never return here.\n" );
      break;
      }
    process ();
    }
  }

process ()
{
  int i;

  printf ( "Input a number to simulate an error condition: " );
  fflush ( stdout );
  scanf ( "%d", &i );
  i %= 3;
  i++;                /* So that we call longjmp with  0 < i < 4 */
  longjmp ( save, i);
  }

  Although in this silly little demo the call to longjmp is in the same file
as the call to setjmp, this does not have to be the case, and in the practical
situation the call to longjmp will be a long way from the call to setjmp. The
mechanism is that setjmp saves the entire state of the computer's CPU in a
buffer declared in the jmp_buf save; statement and longjmp restores it exactly
with the exception of the register which carries the return value from longjmp.
This value is the same as the second argument in the longjmp call - i in our
little demo. This means, of course, that the stack and frame pointer registers
are reset to the old values and all the local variables being used at the time
of the longjmp call are going to be lost forever. One consequence of this is
that any pointer to memory allocated from the heap will also be lost, and
you will be unable to access the data stored in the buffer. This is what the
jargonauts call "memory leakage", and is really very difficult bug to find.
Your program runs out of dynamic memory long before it should. Take care.
So you have to keep a record of the buffers' addresses and free them
before the call to longjmp.

More details later on when we learn about the heap memory allocation routines.


                                   Lesson 9.

                             Pointers in Practice.
                     Structures and an example of their use.

  This lesson is a description and a demonstration of a doubly linked list
data structure. It is presented as a program which is - if such a thing
is possible - somewhat over commented.

  The "Points to note" about this offering are:

  1) It demonstrates the concepts of a doubly linked list.
     For simplicity the data object in this lesson is a string of characters.
     It is, of course quite possible to alter the data structure definition
     so that the the data pointer points at a different data type.

  2) The entire data structure is hidden from the user of the 
     functions, by making the structure which contains the pointers
     defined as "static". This has the advantage that "self appointed
     experts" cannot get at the data structure unless they alter the
     source code in the source file. In a practical production situation
     the permissions on the source would prevent this.
     It's the beginnings of object orienteering!

  3) The ends of the list are marked by NULL pointers in the
		 pointers.current->forward or pointers.current->back linking
		 pointers in the list as well as the pointers.head and pointers.tail
		 pointers being set to NULL by the functions when current is at either
		 head or tail.

  4) There is no need to have the definitions of either the pntrs,
     or the dl_node data structures in a header file as all the
     access to data is performed through the provided functions,
     which are listed here.

     For the whole story refer to the comments in the source.

  5) Have a look in the code and note all the special cases which
     have to be looked after. It was something of a revelation to me!

   dl_delete_node  Delete the current node.
   dl_destroy      Destroy the whole list.
   dl_get_data     Get the pointer to the data element.
   dl_insert_node  Insert a node into the list.
   dl_is_head      Is the current list element the head?
   dl_is_tail      Same for tail.
   dl_print_list   Print the whole of the list to stdout. ( Mostly for debug )
   dl_head         Set pointers so as to make current the head element.
   dl_tail         Set pointers so as to make current the tail element.

     Here is a little diagram which hopefully gets the concepts across.

 The Pointers.      The list of                 The data elements
                   linked nodes.

                +------------------+
                | NULL             |
                +------------------+   +------------------------------------+
       +------->| Data Pointer     |->-| "So let it be with Caesar."        |
       |        +------------------+   +------------------------------------+
       |        | Back Pointer     |
       |        +------------------+
       |            |          |                 
       |            ^          v
       |            |          |
       |        +------------------+
       |        | Forward Pointer  |
       |        +------------------+   +------------------------------------+
       |   +--->| Data Pointer     |->-| "The good is oft interred with ..."|
       |   |    +------------------+   +------------------------------------+
       |   |    | Back Pointer     |
       |   |    +------------------+
       |   +--+     |          |
  +----+----+ |     ^          v
  | Head    | |     |          |
  +---------+ | +------------------+
  | Next    |-+ | Forward Pointer  |
  +---------+   +------------------+   +------------------------------------+
  | Current |->-| Data Pointer     |->-| "The evil that men do lives .... " |
  +---------+   +------------------+   +------------------------------------+
  | Previous|-+ | Back Pointer     |
  +---------+ | +------------------+
  | Tail    | |     |          |
  +---------+ |     ^          v
       |   +--+     |          |
       |   |    +------------------+
       |   |    | Forward Pointer  |
       |   |    +------------------+   +------------------------------------+
       |   +--->| Data Pointer     |->-| "I come to bury Caesar, not to .." |
       |        +------------------+   +------------------------------------+
       |        | Back Pointer     |
       |        +------------------+
       |            |          |
       |            ^          v
       |            |          |
       |        +------------------+
       |        | Forward Pointer  |
       |        +------------------+   +------------------------------------+
       +------->| Data Pointer     |->-| "Friends, Romans, Countrymen, ..." |
                +------------------+   +------------------------------------+
                | NULL             |
                +------------------+

  This diagram illustrates the storage arrangements used in the test
program to store the famous text fragment as a doubly linked list. 
Note that the pointers in the pointer structure point at the node in the 
list as a whole and not specifically at the Data Pointer therein.
Please don't get confused if you look at the actual values with a debugger.

  Friends, Romans, Countrymen, lend me your ears;
  I come to bury Caesar, not to praise him
  The evil that men do lives after them;
  The good is oft interred with their bones;
  So let it be with Caesar.

               Thank you William Shakespere.

  In this case we have only five line of text, but it is more than possibe to 
have many thousands of elements in the list.

  First of all cut the program and header files out of the news file
and compile them with the pre-processor symbol "TESTING" defined on the
shell command line:-

$ cc -g -DTESTING -o dl_list dl_list.c
      ^
The -g flag makes the compiler output symbol table and source code line
number information into the output file for a debugger program to use.

Now set the program to execute.

$ ./dl_list

And press the SIGINT key as soon as the output appears so that unix
creates a "core" memory dump file.

$ sdb ./dl_list   ( You may have a different debugger on your machine. )

Put some suitable break points in the program and watch the pointers
change as the program statements are executed.

  For production use you can compile the program with the optimiser on
and the testing program removed.

$ cc -DENGLISH -c dl_list.c

  The prep. / homework exercise is to study this program carefully.
Appreciate that this code can implement the functions of these
data structures: A stack, a first in first out queue, & a sorted list.
Now think about making a program which uses these functions.

Yet another editor is also a possibility.

  Another more advanced exercise would be to write a function as part
of this file which sorts the list according to some defined criterion.
Hint: Use an external function which is called from within this suit of
programs using a function pointer.

  Refer to a good textbook on the subject of sorting and searching.

  I can recommend Donald Knuth: Art of Computer Programming.

	Refer to the C Users Journal, August 1993 - Vol. 11 No. 8, pp 53 - 58.
		This article by Roger Meadows is a good explanation of the testing
		techniques presented here.

  Note that whilst I have taken every care to try to ensure that this code is
both usable and free of bugs; it comes to you as a gift and like every thing
else which is free it is offered without any warranty whatsoever.

 /* -------------------------------cut here--------------------------------- */

#ident "@(#) dl_list.h - Header file.

#define ERROR (1)
#define SUCCESS (0)

#define FORWARD (1)
#define BEFORE (1)
#define STILL (0)
#define AFTER (-1)
#define BACKWARD (-1)

 /* -------------------------------cut here--------------------------------- */

#ident "@(#)dl_list.c - Routines for doubly linked list"

#if defined(TESTING)
# include <signal.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <assert.h>
#include "dl_list.h"

static char *error_messages[] =
{
#if defined(LANGUAGE)
# if ( LANGUAGE == ENGLISH )
  "head",
  "tail",
  "BEFORE the",
  "AFTER the",
  "\nYou cannot delete the",
  "node and leave current",
  "of the list\n",
  "Linked List Empty\n",
  "Direction must be either BEFORE or AFTER\n"
# endif
#else
  error "LANGUAGE must be defined"
#endif
  };

extern void perror();
extern void exit();

enum boolean { FALSE, TRUE };
typedef enum boolean BOOLEAN;

/*
** These structure definitions are in this file - as opposed to a header
** file because there is no need for the users of the routine to know
** anything about them. The list is accessed through the functions.
*/

struct dl_node
{
  struct dl_node *forward, *back;
  char *data;
  };

typedef struct dl_node DL_NODE;

/*
** I put all the pointers in a struct so that it is easy to examine them
** all at the same time with just one command to the debugger.
** It is not, for the operation of the program, strictly necessary to
** have previous and next pointers in this structure, but it certainly
** makes debugging so very much easier!
*/

struct pntrs
{
  DL_NODE *head,      /* Pointer to head of list. */
          *next,      /* Pointer to next, i.e. nearer to head, element. */
          *current,
          *previous,  /* Pointer to previous, i.e. nearer to tail, element. */
          *tail;      /* Pointer to tail of list. */
  };

static struct pntrs pointers =  /* Declared "static" so the hoi-poloi */ 
{                               /*  can't fiddle about with them!     */
  ( DL_NODE *) NULL,            /* This technique is "data-hiding"    */
  ( DL_NODE *) NULL,            /* Explicitly NULL for use as an      */
  ( DL_NODE *) NULL,            /* initialisation flag.               */
  ( DL_NODE *) NULL,
  ( DL_NODE *) NULL
  };

/*
** Set current to the head of the list.
*/

void dl_head()
{
  if ( pointers.current == ( DL_NODE * ) NULL )
  {
    ( void ) fprintf ( stderr, "dl_head: %s", error_messages[7] );
    exit ( ERROR );
    }
  if ( pointers.current == pointers.head ) return;
  pointers.next = ( DL_NODE * ) NULL;
  pointers.current = pointers.head;
  pointers.previous = pointers.current->back;
  }

/*
** Test to see if current is at the head of the list.
*/

BOOLEAN dl_is_head()
{
  return ( pointers.current == pointers.head ? TRUE : FALSE );
  }

/*
** Set current to the tail of the list.
*/

void dl_tail()
{
  if ( pointers.current == ( DL_NODE * ) NULL )
  {
    ( void ) fprintf ( stderr, "dl_tail: %s", error_messages[7] );
    exit ( ERROR );
    }
  if ( pointers.current == pointers.tail ) return;
  pointers.previous = ( DL_NODE * ) NULL;
  pointers.current = pointers.tail;
  pointers.next = pointers.current->forward;
  }

/*
** Test to see if current is at the tail of the list.
*/

BOOLEAN dl_is_tail()
{
  return ( pointers.current == pointers.tail ? TRUE : FALSE );
  }

/*
** Return the current data element.
*/

char *dl_get_data( d )
int d;
{
  switch ( d )
  {
case STILL:
    {
      break;
      }
case FORWARD:
    {
      if ( pointers.next == NULL ) return NULL;
      pointers.previous = pointers.current;
      pointers.current = pointers.next;
      pointers.next = pointers.current->forward;
      break;
      }
case BACKWARD:
    {
      if ( pointers.previous == NULL ) return NULL;
      pointers.next = pointers.current;
      pointers.current = pointers.previous;
      pointers.previous = pointers.current->back;
      break;
      }
    }
  return pointers.current->data;
  }

/*
** Insert a node into the linked list.
** p is a pointer to the data string you wish to add to the list.
** d is a direction indicator. BEFORE or AFTER the current position.
*/ 

void dl_insert_node( p, d )
char *p;
int d;
{
  DL_NODE *nn_p;
  char *nd_p;

  /*
  ** Allocate dynamic memory for node.
  */

  nn_p = ( DL_NODE * ) malloc ( sizeof ( DL_NODE ));

  /*
  ** Also for the data associated with the node.
  */

  nd_p = ( char * ) malloc ( ( size_t ) strlen ( p ) + 1 );

  /*
  ** Check that we could have it.
  */

  if ( nn_p == ( DL_NODE *) NULL || nd_p == ( char * ) NULL )
  {
    perror ( "dl_insert_list" );
    exit ( ERROR );
    }

  /*
  ** Copy the string of characters into the heap or dynamic memory.
  */

  ( void ) strcpy ( nd_p, p );

  /*
  ** First test to see if this is the first call.
  */

  if ( pointers.current == ( DL_NODE * ) NULL )
  {
    pointers.head = pointers.current = pointers.tail = nn_p;
    nn_p->forward = nn_p->back = ( DL_NODE * ) NULL;
    nn_p->data = nd_p;
    return;
    }

  if ( pointers.current == pointers.head && d == BEFORE )
  {
    pointers.previous = pointers.current;
    pointers.previous->forward = pointers.head = pointers.current = nn_p;
    pointers.current->back = pointers.previous;
    pointers.next = pointers.current->forward = ( DL_NODE * ) NULL;
    }
  else
  if ( pointers.current == pointers.tail && d == AFTER )
  {
    pointers.next = pointers.current;
    pointers.next->back = pointers.tail = pointers.current = nn_p;
    pointers.current->forward = pointers.next;
    pointers.previous = pointers.current->back = ( DL_NODE * ) NULL;
    }
  else
  if ( ( pointers.current != pointers.head ) && 
       ( pointers.current != pointers.tail )
       )
  switch ( d )
  {
case BEFORE:
    {
      pointers.current->forward = pointers.next->back = nn_p;
      pointers.previous = pointers.current;
      break;
      }
case AFTER:
    {
      pointers.current->back = pointers.previous->forward = nn_p;
      pointers.next = pointers.current;
      break;
      }
    }
  pointers.current = nn_p;
  pointers.current->forward = pointers.next;
  pointers.current->back = pointers.previous;
  nn_p->data = nd_p;
  }

/*
** Remove a node at current position.
** dir is the direction flag, it takes the value BEFORE or AFTER.
** BEFORE tells the function to move current closer to the head of the list.
** AFTER    "    "     ""    ""  ""     "      ""   ""  "  tail ""  "   ""
** Return ERROR if errors, SUCCESS otherwise.
*/

int dl_delete_node( dir )
int dir;
{
  DL_NODE *cn_p;
  char *cd_p;

  /*
  ** Check that there is something to delete.
  */

  if ( pointers.current == ( DL_NODE * ) NULL)
  {
    ( void ) fprintf ( stderr, "dl_delete_node: %s", error_messages[7] );
    return ( ERROR );
    }

  /*
  ** Check that they have given us a correct direction.
  */

  if (( dir != BEFORE ) && ( dir != AFTER ))
  {
    ( void ) fprintf ( stderr, "dl_delete_node: %s", error_messages[8] );
    return ( ERROR );
    }

  /*
  ** Save the pointers to the heap memory allocated by malloc.
  */

  cn_p = pointers.current;                  /* Pointer to linked list node. */
  cd_p = pointers.current->data;            /* Pointer to data area.        */

  /*
  ** "Re-connect" the pointers in the list and in pointers structure. 
  ** There are four different situations for which we have to cater.
  ** In order: 1) There is only one item in the list.
  **           2) The current item in the list is the first, 
  **           3)  "     "     ""  ""  "   ""  ""  "  last,
  **           4) The normal one with other items on either side.
  */

  /*
  ** Special condition 1: The current element is the only one.
  */

  if ( pointers.head == pointers.tail )
  {
    ( void ) memset ( &pointers, 0, sizeof pointers );
    }
  else

  /*
  ** Special condition 2: The current element is the head one.
  */

  if ( dl_is_head())
  {
    if ( dir == BEFORE )
    {
      ( void ) fprintf ( stderr,
                         "dl_delete_node: %d %s %s %s %s %s %s",
                         __LINE__,
                         error_messages[4],
                         error_messages[0],
                         error_messages[5],
                         error_messages[2],
                         error_messages[0],
                         error_messages[6]
                         );
      return ( ERROR );
      }

    pointers.head = pointers.current = pointers.previous;
    pointers.previous = pointers.current->back;
    pointers.current->forward = ( DL_NODE * ) NULL;
    }
  else

  /*
  ** Special condition 3: The current element is the tail one.
  */

  if ( dl_is_tail())
  {
    if ( dir == AFTER )
    {
      ( void ) fprintf ( stderr,
                         "dl_delete_node: line: %d %s %s %s %s %s %s",
                         __LINE__,
                         error_messages[4],
                         error_messages[1],
                         error_messages[5],
                         error_messages[3],
                         error_messages[1],
                         error_messages[6]
                         );
      return ( ERROR );
      }

    pointers.tail = pointers.current = pointers.next;
    pointers.next = pointers.current->forward;
    pointers.current->back = ( DL_NODE * ) NULL;
    }
  else

  /*
  ** This is the normal situation.
  */

  if ( !dl_is_head() && !dl_is_tail() )
  {
    pointers.next->back = pointers.previous;
    pointers.previous->forward = pointers.next;
    pointers.current = ( dir == BEFORE ) ? pointers.next : pointers.previous;
    pointers.previous = pointers.current->back;
    pointers.next = pointers.current->forward;
    }

  /*
  ** Return the dynamic or heap memory to the operating system.
  */

  ( void ) free ( cn_p );
  ( void ) free ( cd_p);
  return ( SUCCESS );
  }

/*
** Destroy the whole list and return all allocated memory to system.
*/

void dl_destroy()
{
  int direction = STILL;
#if(!defined(KILL_DIRECTION) || KILL_DIRECTION == FORWARD)
  dl_tail();
  while ( pointers.current != ( DL_NODE * ) NULL )
  { 
    direction = ( dl_is_head() ) ? AFTER : dl_is_tail() ? BEFORE : direction;
    ( void ) dl_delete_node( direction );
    }
#else
  dl_head();
  while ( pointers.current != ( DL_NODE * ) NULL )
  {
    direction = ( dl_is_tail() ) ? BEFORE : dl_is_head() ? AFTER : direction;
    ( void ) dl_delete_node( direction );
    }
#endif
  }
 
/*
** Print entire list in desired direction.
*/

dl_print_list(d)
int d;
{
  char * ch_p;

  if ( d == FORWARD ) dl_tail(); else dl_head();
  ch_p = dl_get_data ( STILL );
  do
  {
    ( void ) printf ( "%s\n", ch_p );
    ch_p = dl_get_data ( d );
    } while ( ch_p );
  }

#if defined(TESTING)
/*
** This is code to test the linked list functions.
** Leave it in the file even after the programs have been put
** into a production status. Mr. Murphy decrees that something will
** go wrong later! You might want to enhance things and break
** this code and if you have thrown away this test code you
** will only have to waste time recreating it!
*/

/*
** Signal handler so it is possible to 
** abort and dump core on receipt of a SIGINT signal.
** This means that it is possible to interrupt continuous
** loop bugs by pressing the the SIGINT key. A core image
** is also produced for later use by sdb or whatever.
*/

static void catch_signal()
{
  ( void ) abort();
  }

char *test_text[] =        /* Thank you William Shakespere. */
{
  "Friends, Romans, Countrymen, lend me your ears;",
  "I come to bury Caesar, not to praise him",
  "The evil that men do lives after them;",
  "The good is oft interred with their bones;",
  "So let it be with Caesar."
  };

#define NUMBER_OF_LINES ( sizeof ( test_text ) / sizeof ( test_text[0] ))

int main()
{
  char *nla = "********* New Line AFTER *********";
  char *nlb = "********* New Line BEFORE *********";
  char *nlh = "********* New Line BEFORE head *********";
  char *nlt = "********* New Line AFTER tail *********";
  char *err1 =
"dl_tail: line: %4d:\n\
      current is: %s\n\
         data is: %s\n"; 
  char *err2 =
"dl_get_data FORWARD line %4d:\n\
                  current is: %s\n\
                     data is: %s\n"; 
  char **ch_pp;
  char *ch_p;
  int i;
  BOOLEAN flag;
  char tmp = '\0';
  char *header = 
  "\n\nCreate a doubly linked list and print out the text as we go.\n\n";

  /*
  ** Setup SIGINT catch.
  */

  signal ( SIGINT, catch_signal );

  /*
  ** Create a doubly linked list and print out the text as we go.
  */

  ( void ) printf ( header );

  for ( ch_pp=test_text, i=0; i < NUMBER_OF_LINES; ch_pp++, i++ )
  {
    ( void ) printf ( "%s\n", *ch_pp );
    dl_insert_node ( *ch_pp, BEFORE );
    assert ( pointers.current->forward == pointers.next &&
             pointers.current->back == pointers.previous
             );
    }

  ( void ) printf ( "\n\nTesting commencing...\n\n" );

/*
** Set current to the tail of the list. Test it.
*/

  dl_tail();                        
  if ( pointers.current == pointers.tail )
  {
    ( void ) printf ( "dl_tail(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_tail(): ... FAILURE" );
    }

  if ( dl_is_tail() )
  {
    ( void ) printf ( "dl_is_tail(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_is_tail(): ... FAILURE" );
    }
  
/*
** Set current to the head of the list. Test it.
*/

  dl_head();                        
  if ( pointers.current == pointers.head )
  {
    ( void ) printf ( "dl_head(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_head(): ... FAILURE" );
    }

  if ( dl_is_head() )
  {
    ( void ) printf ( "dl_is_head(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_is_head(): ... FAILURE" );
    }

/*
** Set current to the tail of the list. Test it.
*/  

  dl_tail();
  ch_p = dl_get_data ( STILL );
  i = strcmp ( ch_p, test_text[0] );
  switch ( i )
  {
case 0:
    {
      ( void ) printf ( "dl_get_data ( STILL ); ... ok\n" );
      break;
      }
default: ( void ) fprintf ( stderr, err1, __LINE__-5, ch_p, test_text[0] );
    }

  flag = FALSE;
  for ( i = 1; i < NUMBER_OF_LINES; i++ )
  {
    ch_p = dl_get_data ( FORWARD );
    switch ( strcmp ( ch_p, test_text[i] ) )
    {
case 0:
      {
        break;
        }
default: 
      {
        flag = TRUE;
        ( void ) fprintf ( stderr, err2, __LINE__-5, ch_p, test_text[i] );
        }
      }
    }
    if ( !flag ) ( void ) printf ( "dl_get_data ( FORWARD ); ... ok\n" );

  if ( pointers.current != pointers.head )
  {
    ( void ) fprintf ( stderr,
             "current not at head when it should be, line: %d\n",
             __LINE__
             );
    }

  dl_head();
  ch_p = dl_get_data ( FORWARD );
  if ( ch_p == ( char *) NULL )
  {
    ( void ) printf ( "attempt to read data BEFORE head node: ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr,
              "attempt to read data BEFORE head node gave: %s\n",
              ch_p
              );
    }

  dl_tail();
  ch_p = dl_get_data ( BACKWARD );
  if ( ch_p == ( char *) NULL )
  {
    ( void ) printf ( "attempt to read data AFTER tail node: ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr,
              "attempt to read data AFTER tail node gave: %s\n",
              ch_p
              );
    }

  dl_head();
  flag = FALSE;
  ch_p = dl_get_data ( STILL );
  for ( i = NUMBER_OF_LINES - 1; i; i-- )
  {
    switch ( strcmp ( ch_p, test_text[i] ) )
    {
case 0:
      {
        break;
        }
default:
      {
        flag = TRUE;
        ( void ) fprintf ( stderr, err2, __LINE__-5, ch_p, test_text[i] );
        }
      }
    ch_p = dl_get_data ( BACKWARD );
    }
  if ( !flag ) ( void ) printf ( "dl_get_data ( BACKWARD ); ... ok\n" );

  ( void ) printf ( "\nTesting dl_insert_node...\n" );
  dl_head();
  ( void ) dl_insert_node ( nlh, BEFORE );
  for ( i = 0; i < 2; i++ ) ( void ) dl_get_data ( BACKWARD );
  ( void ) dl_insert_node ( nlb, BEFORE );
  for ( i = 0; i < 2; i++ ) ( void ) dl_get_data ( BACKWARD );
  ( void ) dl_insert_node ( nla, AFTER );
  dl_tail();
  ( void ) dl_insert_node ( nlt, AFTER );

  ( void ) printf ( "\nPrinting List with inserted lines...\n\n" );

  dl_tail();
  dl_print_list( FORWARD );

  ( void ) printf ( "\nRemoving lines just inserted...\n\n" );
  dl_tail();
  ch_p = dl_get_data ( STILL );
  do
  {
    if ( strncmp ( ch_p, nla, 8 ) == 0 )
    { int direction;
      direction = ( dl_is_head() ) ? AFTER : BEFORE ;
      ( void ) dl_delete_node( direction );
      }
    ch_p = dl_get_data ( FORWARD );
    } while ( ch_p );

  ( void ) printf ( "\nPrinting list after removal of inserted lines...\n\n" );
  dl_print_list( FORWARD );
  ( void ) printf ( "\nGoing to destroy list...\n" );
  dl_destroy();

  /*
  ** Check that pointers structure is all zero.
  */

  for ( i = 0, ch_p = ( char * ) &pointers;
        i < sizeof ( DL_NODE ) && tmp == '\0';
        i++, ch_p++
        )
  {
    tmp |= *ch_p;
    }

  if ( tmp == '\0' )
  {
    ( void ) printf ( "\ndl_destroy: List destroyed ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_destroy: FAILED\n" );
    }
  ( void ) printf ( "\nTesting Completed...\n\n" );
  return (0);
  }
#endif

 /* -------------------------------cut here--------------------------------- */

  Enjoy!

Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.


--
 +-----------------------------------------------------------------------+
 | NAME   Christopher Sawtell                                            |
 | SMAIL  215 Ollivier's Road, Linwood, Christchurch, 8001. New Zealand. |
 | EMAIL  chris@gerty.equinox.gen.nz                                     |
 |    Learn C programming, or English by e-mail - write for details.     |
 +-----------------------------------------------------------------------+

