


			 C For Assembler Coders!
			 =======================


			     By Dave Edwards
			     ===============


				Part 3
				======


Contents:
---------

Introduction

C : Structures

C : Unions

C : Include Files

C : Typedef

C : Preprocessor Commands


Introduction
------------

	Having gone through the torture of C pointers, I now propose to put
ACC members through yet more torture. Namely the dread area of C structures.
Actually, structures are not that dread an area - at least if you're one of
those well-behaved ACC assembler coders who uses Devpac RS definitions!


C : Structures
--------------


	A C structure is simply a way of grouping data together which belongs
together in some sense, even if the data types of the individual items differ
from each other. The prime example is name, address, telephone number, sex,
marital status etc., as a record in a database.


	Well, how would you do it in assembler? I'd use the following RS def-
initions in the simplest case:


		rsreset
name		rs.l	1	;ptr to name text
address		rs.l	1	;ptr to address text(s)
telephone	rs.b	10	;10 digit telephone number
sex		rs.b	1	;1 byte:male/female M/F
status		rs.b	1	;1 byte:(S)ingle/(M)arried etc


Now compare the C structure:


struct record	{

		char *name;		/* pointer to name */
		char **address;		/* pointer to address text(s) */
		char telephone[10]	/* 10-digit tel. no */
		char sex;		/* M/F */
		char status;		/* S/M/W/D (work it out) */

		}


This acts almost exactly like a Devpac RS declaration. It defines each of the
data items, and the order in which they appear. The above does NOT reserve a
variable of that type, though:it simply defines what the structure looks like
just as an RS definition defines how an area of memory will be accessed with-
out reserving the actual memory area.


	The structure defintion consists of the keyword 'struct', followed by
an optional name for the structure type. Here I've called my example struct-
ure 'record', but virtually any sensible name can be chosen. Then, a set of
the now ubiquitous '{}' braces, containing the structure elements in the or-
der of appearance.


	This tells a C program how a structure looks. How is a structure var-
iable defined? The example below answers this question:


struct record	{

		char *name;		/* pointer to name */
		char **address;		/* pointer to address text(s) */
		char telephone[10]	/* 10-digit tel. no */
		char sex;		/* M/F */
		char status;		/* S/M/W/D (work it out) */

		}

	/* the above just tells the compiler what the structure */
	/* looks like */

main()

{

	struct record person;	/* creates the actual variable */

	/* rest of program from here on */

}


So, the structure definition goes in front of the definition of 'main'. Then
the label attached to the structure definition (here 'record') is used in the
definition of an actual variable of that type.


	Needless to say, it is possible to define structure types without a
name, but it makes using them a bit harder. Almost all C structures appearing
in a program have a name of some sort, and on the Amiga, EVERY structure has
a name devised by the authors of the system software (much of which was actu-
ally written in C, just like UNIX).


	Ok, having defined a structure, what do we do with it? Well, should
our record need some data entering into it, the method of data entry is:


	person.name = "Joe Bloggs";

	person.address = { "12, Acacia Avenue",
			 "Newbury",
			 "Berkshire" }

	person.telephone = { 0,7,2,7,4,3,1,2,5,6 } ;

	person.sex = 'M';

	person.status = 'S';


Note how both arrays (here person.telephone) and pointers to pointers to char
strings are initialised. An array can be initialised simply by listing all of
its initial values, separated by commas, in '{}' braces. So can the structure
item 'person.address', by enclosing all of the component strings in the same
braces.


	Basically, any member of a structure is accessed by the form:


		structure_name.member


and this construct can appear on either the left hand or right hand side of
an assignment, as in the expressions (assuming all variables have been defin-
ed accordingly):


		tmpsex = person.sex ;

		person.status = tmpstatus;


But there's an alternative. The WHOLE STRUCTURE can be initialised with a set
of constants in braces, as in the following example, PROVIDED THAT THE STRUC-
TURE DEFINITION IS A STATIC STRUCTURE:


main()

{	/* assume that 'struct record' defined as above */

	static struct record person;


	person = { "Joe Bloggs",

		  { "12, Acacia Avenue",
		    "Newbury",
		    "Berkshire" } ,

		  { 0,7,2,7,4,3,1,2,5,6 } ;

		  'M',

		  'S' }

}


(Note the nested braces for the char ** member!). This kind of initialisation
occurs regularly in Amiga C programs, particularly for structures such as the
NewScreen structure passed to the Intuition OpenScreen() call.


	And yes, not only can initialisation braces be nested, but structure
definitions can be nested. We can define a new structure as follows:


struct extrecord	{

		 struct record employee; /* a complete record structure */

		 int jobcode;

		 int salary;

		 char *taxcode;

		}
		 
and initialise that in a like manner. But now, the initialisation braces for
a structure variable of type extrecord will have to contain the ENTIRE set of
initialisation braces for 'person' above, for example, plus the new data! And
since many Amiga library routines (particularly intuition and graphics libra-
ry routines) use structures that contain other structures embedded in them,
expect to see a lot of this sort of code in Amiga C programs.


	Oh, and as might be expected, if we have a variable defined as:


		struct extrecord worker;


then we access this record's details using constructs such as:


		worker.jobcode = 275;	/*set worker's job code */

		worker.employee.status = 'M'; /* changed marital status */


and note the construct 'worker.employee.status' to refer to a member of the
structure within the structure. Structures can be nested within each other to
an arbitrary depth (!!!) so watch out for this kind of construct.


	So, what can you do with a structure? Well, you can access a member
of the structure (or include a member in a complex expression if you wish),
or obtain its address via &. So, under the original K&R C specification, you
can't copy whole structures as a unit unfortunately, so that:


	struct record person1, person2 ;

	/* here initialise person1 */

	person1 = person2 ;


is illegal. I haven't had time to check this completely, but I think that it
is illegal in ANSI C too. So to copy a structure, it must be done member by
member (sadly). However, one can define POINTERS to structures, just as one
can define pointers to simple variables, using definitions such as:


		struct record *life_form;


and having done so, if a structure exists (such as 'person' above), we can
perform the following:


		life_form = &person;


Pointers to structures are used EXTENSIVELY in Amiga C, so get used to them!


	Having defined our pointer to our structure, we can still access the
members of the structure, using:


		tempsex = (*life_form).sex


However, this kind of access occurs so frequently that a special symbol has
been set aside for this operation, namely:


		tempsex = life_form->sex


And just to wind up pointers to structures, how about the following example:


struct date	{
		  UBYTE day;	/* this example uses the special */

		  UBYTE month;	/* Amiga C data types */

		  ULONG year;

		}


struct time	{
		  UBYTE hours;

		  UBYTE minutes;

		  UBYTE seconds;

		}

struct alarm	{

		  struct date *date;

		  struct time *time;

		  ULONG flags;

		}


main()

{

	struct alarm *clock;	/* define a ptr to the structure */


	clock = (struct alarm *) AllocMem( (ULONG) sizeof(struct alarm),
					MEMF_PUBLIC);

	clock->date = (struct date *) AllocMem( (ULONG) sizeof(struct date),
					MEMF_PUBLIC);

	clock->time = (struct time *) AllocMem( (ULONG) sizeof(struct time),
					MEMF_PUBLIC);

	clock->date->day = 24;

	clock->date->month = 1;

	clock->date->year = 1992L; /* specifies a LONG constant */


}


Oh, yes. You'll see LOTS of this type of code in Amiga C programs!


	Some explanations. Remember CASTING (from tutorial 1)? This is the
method of forcing variable type conversions. The cast (struct alarm *) in the
first line of actual code forces the result of the AllocMem() call to be con-
verted from its normal type (APTR) to the special pointer type required, i.e.
a pointer to a special structure. If you DON'T do this, the compiler will be
rude and spit out 'type mismatch' error messages! Same for the (ULONG) casts
in the AllocMem calls:AllocMem expects a ULONG as its first argument, so to
make sure, the cast forces the result of sizeof() in each case to be convert-
ed thus. Oh, yes, the sizeof() function works on structures too!


	So, the first AllocMem() call reserves memory for the 'alarm' struc-
ture, and initialises our pointer variable to point to it. Then, the next two
AllocMem() calls reserve memory for the time and date structures, and set up
the pointers clock->date and clock->time to point to the memory areas reser-
ved thus. Now we can start filling in the structures! Note that in this code,
and in all C code, -> and . work from left to right within a structure-member
reference, in the (reasonably) commonsense fashion.


	Having thrown this at you, how about the assembler equivalent? Well,
it looks something like this:


		rsreset
date_day		rs.b	1
date_month	rs.b	1
date_year	rs.l	1
date_sizeof	rs.w	0	;because assembler doesn't have a
				;sizeof() built-in like C!

		rsreset
time_hours	rs.b	1
time_mins	rs.b	1
time_secs	rs.b	1
time_filler	rs.b	1	;force even align on 68000
time_sizeof	rs.w	0

alarm_date	rs.l	1	;ptr to date struct
alarm_time	rs.l	1	;ptr to time struct
alarm_flags	rs.l	1
alarm_sizeof	rs.w	0

		rsreset
clock		rs.l	1	;main variables
vars_sizeof	rs.w	0



main		lea	vars(pc),a6

		move.l	#alarm_sizeof,d0
		move.l	#MEMF_PUBLIC,d1
		CALLEXEC	AllocMem		;equivalent to
		move.l	d0,clock(a6)	;clock = (struct alarm *) ...

		move.l	#date_sizeof,d0
		move.l	#MEMF_PUBLIC,d1
		CALLEXEC	AllocMem
		move.l	clock(a6),a0	;equivalent to the code
		move.l	d0,alarm_date(a0)	;clock->date = ...

		move.l	#time_sizeof,d0
		move.l	#MEMF_PUBLIC,d1
		CALLEXEC	AllocMem
		move.l	clock(a6),a0	;equivalent to the code
		move.l	d0,alarm_time(a0)	;clock->time = ...

		move.l	clock(a6),a0
		move.l	alarm_date(a0),a1	;equivalent to
		move.b	#24,date_day(a1)	;clock->date->day = 24;

		rts		

vars		ds.b	vars_sizeof


As you can see, there's a good correspondence between C and assembler on the
68000. For complex algorithms, C tends to be chosen because it's quicker to
debug than assembler, and sacrifices only a small amount of speed, being a
compiled language. But most C compilers tend to generate code which passes
parameters to functions on the stack. This slows things down when Intuition
or graphics library functions are heavily used, and matters get worse when a
recursively written C function is called, so take heart, ACC coders, 68000
assembler still deserves its loyal following!


	Having taken you on a tour of structures, one final feature of C is
left to cover. Unions. So here goes.


C : Unions
----------


	If I tell you that union definitions look just like structure defin-
itions, except for the keyword 'union', you'll probably wonder what the fuss
is about.


	Well, a union in C is a way of using one memory location (of a size
compatible with the data types involved) for several different purposes. This
is a trick that assembler coders are fond of, especially in games code, so it
comes as no surprise to find C unions cropping up in Amiga C code.


	An example serves to explain this:


union msg	{
		  ULONG im_IAddress;

		  struct mousedata	{
					  UWORD mousex;

					  UWORD mousey;

					} im_MouseData;
		}

In this case, I am declaring that any variable defined as being of this type
can be thought of as being two different objects, depending upon what I want.
I can either refer to it as a unit called 'IAddress', or as a unit which just
happens to be a 'mousedata' structure. This way, I can save a bit of memory
if I know for certain that I'll never encounter the two different data types
simultaneously. Note that the definition of the 'mousedata' structure occurs
inside the union. If I only want to refer to a mousedata structure within a
larger structure, and have no use for it outside, I can embed the definition
inside as above, and be sure that it can only be referred to as part of the
above union, and nowhere else. Otherwise I'd have defined 'mousedata' else-
where, and simply had:


	struct mousedata im_MouseData;


in the union definition, making the 'mousedata' structure globally available.


	When encountering a variable of this type, the compiler will check
the memory requirements of the various components, and reserve enough space
to store the largest. Here, both components happen to be the same size, but
if I later added an extra entry to the 'mousedata' structure then the compi-
ler would reserve more memory to take account of this whenever a variable of
this type was defined. So I can now define:


union msg message;


in a C program, and refer to 'message' either as:


		message.IAddress;


or as:


		message.mousedata.mousex;

		message.mousedata.mousey;


Handy, huh?


	Those ACC members with the old copy of the Amiga System Programmers'
Guide (before they covered semaphores etc., in detail), i.e., the March 1989
second printing issue, will notice a union definition in Chapter 2.5 on Amiga
memory management (the MemList structure contains a MemEntry structure which
in turn contains a union). Other unions appear in other structures, but this
is probably the first place that ACC coders will find a union on the Amiga.


	As shown above, unions have an identical syntax to structures when
accessed within a program. You can even define pointers to them and access a
union via:


		union_pointer->member


just as for structures!


C : Include Files
-----------------


	Since Devpac allows include files in assembler, it seems reasonable
for C to allow them too. Well, it does. To define an include file, there are
two instructions to use. Either use:


		#include "include_file"


if you're including a file of your own devising, or


		#include <stdio.h>


if including one of the standard C library files that come with every C com-
piler (one of the reasons that good C compilers are so expensive is that in-
clude files and libraries are always supplied, and they often take up three
or more floppy discs!). Note that standard C include files often end in .h:
this is a convention carried over from UNIX systems and maintained on the
Amiga. User include files often end in .i to distinguish them, but this is
only a convention - if you wish to be perverse then violate it...


	The standard include files for C include such files as stdio.h, the
standard input/output functions file (home of printf() among others), and on
the Amiga, files with names such as exec/types.h and exec/io.h. By adding a
suitable #include in a C source file, each of these include files can be used
as desired. These files grant the Amiga C programmer access to Exec, the DOS,
graphics and Intuition libraries, and all of the other Amiga system libraries
just as the GenAm include files grant such access to 68000 programmers.


	The # character at the start signals that this command is obeyed by
the 'boy scout' C preprocessor, which is a preparation program through which
all C source is passed prior to being fed into the compiler proper. All of
the C preprocessor commands begin with a # character to distinguish them from
the actual C keywords understood by the C compiler proper. Most modern C com-
pilers are all-in-one affairs with the preprocessor built-in, but some older
systems still have separate preprocessor/compiler programs.


C : Typedef
-----------


	The C keyword 'typedef' works a bit like the PASCAL 'TYPE' keyword,
and allows user programs to define special and meaningful names to complex
types. For example, having defined our structure 'record' in the section on
structures, it is possible to write:


typedef struct record PERSONNEL;


after the structure definition and define our variables as:


	PERSONNEL person;	/* defines a variable of type */
			/* 'struct record' as before */



Similarly, to handle strings, we could write:


typedef char *STRING;


and then define our pointers to character strings as:


		STRING p, q, r;


which is the same as:


		char *p, *q, *r;


This facility exists for two reasons. One, to allow meaningful names to be
assigned to complex data types, especially complex structures such as those
which abound on the Amiga. Two, to make the program portable across a range
of machines, where machine-dependent information can be embedded in the type-
defs, so that ONLY the typedefs need to be changed when the program is moved
to a new machine. Of course, in practice other changes have to be made when
moving a C program from, say, an A500 Amiga to a Control Data mainframe, but
the existence of typedef and the include file facility minimise the work that
is needed.


C : Preprocessor Commands
-------------------------


	Having introduced (briefly) #define, and in greater depth, #include,
I shall now cover the preprocessor commands in some depth, except for the now
familiar #include dealt with above.


	First, let's look at #define again. Basically, this allows a label
to be assigned to a character string, and thus allows various 'magic numbers'
and other constants to be defined symbolically, as in the assembler EQU dir-
ective:


	#define TRUE 1
	#define FALSE 0


are two good examples.


	What happens to C programs containing #define's is this: the C pre-
processor reads the definitions and stores the details in a lookup table of
some sort. Then, every time a label such as TRUE above is encountered in the
program proper, the string (such as the ASCII character '1' in the above ex-
ample) is substituted for the label in the program text during input to the
compiler.


	The preprocessor is clever enough to allow macros to be defined, in
a manner similar to the MACRO assembler directive of Devpac. I quoted the ex-
ample of the square() macro in tutorial 1. However, there are restrictions to
be noted. First, in the definition of:


	#define square(x) (x)*(x)


I had to place brackets around ecvery occurrence of the parameter x, to en-
sure that when a substitution of the form:


	p = square(a+b);


occurred, the parameter was correctly substituted as:


	p = (a+b)*(a+b);


when the preprocessor substituted the text. If the definition had been:


	#define square(x) x*x


then the result of requesting:


	p=squre(a+b)


would have been:


	p=a+b*a+b;


which is NOT what was wanted!


	Another problem with using macros of this sort is a purely lexical
one:there must NOT be a space between the name of the macro 'function' and
the bracket opening the parameter list.


	In combination with #define, there is a #undef command, which forces
the preprocessor to FORGET the assignment defined by the associated #define.
For example, if we have:


	#define YES 1


then later on in a program, we can write:


	#undef YES


to 'forget' the definition.


	Other preprocessor commands allow conditional compilation. The full
list of conditional compilation directives is:


	#if, #else, #endif	complete IF-THEN-ELSE clause

	#ifdef			true if an identifier has been
				defined with a #define

	#ifndef			true if an identifier has NOT
				been defined with #define


As in C code proper, FALSE is taken to be zero, and TRUE to be any nonzero
value. So we can have the following:


	#if expression

		/* this section of code gets compiled */
		/* if the 'expression' is true */

	#else

		/* this section of code gets compiled */
		/* if the 'expression' is false */
		/* a #else clause is optional */

	#endif

		/* normal sequential compilation resumes */
		/* at this point */


The compilation can be directed not only by a #if, but by the #ifdef and the
#ifndef directives, these being valid replacements for #if. Thus compilation
can be directed according to whether a given identifier has been defined or
not. So for example, if a program has been written with code for more than
one machine, it is possible to force compilation of the required version by
simply adding a #define at the top of the source file before compilation, as
in:


	#define CBM_AMIGA 1	/* define the label CBM_AMIGA */
				/* to force compilation of the */
				/* Amiga version of the program */


	#ifdef CBM_AMIGA


		/* this code section get compiled if the */
		/* label CBM_AMIGA has been defined */


	#endif

	#ifdef ATARI_ST


		/* this code section gets compiled if the */
		/* label ATARI_ST has been defined */


	#endif

	#ifdef IBM_PC


		/* this code section gets compiled if the */
		/* label IBM_PC has been defined */


Similarly, one can check for undefined labels needed by the program, and then
force the preprocessor to include another source file (containing the #define
for that missing label) if it has not been defined, as in:


	#ifndef DOS_LIB_I

	#include <libraries/dos_lib.h>

	#endif


which is used to force inclusion of the DOS library file if it hasn't already
been included (on most Amiga C systems).


This wraps up the third C tutorial for ACC members. Hopefully this tutorial
will be of assistance to those wishing to decipher the C source examples in
the official Amiga manuals that aren't covered in assembler by the series of
doc files typed_xxx.doc, also provided by yours truly (this should show you
how I created some of those files!). Have fun with this tutorial series, and
as always,


		Live fast, code hard & die in a beautiful way!



			Dave.




