


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


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


				Part 1
				======


Contents:
---------

Introduction

C : Its History

C : Getting Started

C : A Program That Does Something

C : Control Flow Statements

C : Functions In Depth

C : Variable Scope

C : Defining Constants Etc


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

	Why C for Assembler Coders? Why would an assembler coder want to have
anything to do with C? ACC members can thank Neil Johnston for this series of
articles, and they may have cause to be grateful to him for requesting their
existence.


	Commodore-Amiga, in their infinite (?) wisdom, decided to publish a
series of huge tomes documenting the internal workings of the Amiga's system
software. Now apart from the fact that a full set of such manuals costs al-
most as much as a bare-bones A500, and most coders have had enough problems
getting together the cash for the A500, virtually all of the code examples in
these manuals are written in C. This leaves most assembler coders out in the
cold, so to speak, unless they also have some C experience. While on the sub-
ject of manuals, in the old days of Z80 CP/M systems that cost over a grand,
the technical manuals came free. Progress, Commodore-Amiga???


	This article, therefore, is for you. Once you have digested this art-
icle and those that will follow it, you should know enough about C to be able
to work out what the Commodore-Amiga examples do, and write your own assem-
bler versions, even if you can't write a C program afterwards (but I hope you
will be able to do that also!).


	So, without further ado, let's move on.


C : Its History
---------------

	C was the end product of a chain of evolution, starting with a lan-
guage called BCPL (known today to its critics as Bastard Computer Programming
Language, though that isn't it's real name) dating from around 1970. BCPL was
interpreter-based, like AmigaBASIC (yeuk!), and underwent some changes to be-
come a new language known as B.


	B was an intermediate product, within which new ideas were tried, and
the end result of this experimentation period was C. C differed from its pre-
cursors in being compiler-based, and in having distinct types of data (BCPL
only had the machine word as its data type), but shared the flow-control and
analogous structures.


	C, while not originating as a strongly structured language such as
PASCAL or MODULA-2, anticipated the vogue for structured languages by about
five years. It was originally designed as a means of writing system software
for a range of machines in as portable a manner as possible, and much of the
character of C stems from its earliest associations with the UNIX operating
system, familiar to many university undergraduate hackers who sharpened the
skills of breaching computer security on PDP-11 minicomputers. In fact, most
of the code for the UNIX operating system itself is written in C, only the
machine-specific I/O drivers and development tools being assembler-based. So
C has a lot of fans in the world of larger computers.


	But what can you do with it? Well, it allows machine-level access to
hardware, while still looking like a high-level structured language. However
in trying to get the best of both worlds, occasionally something has to give
and in the case of C, readability went up the spout in the hands of some of
the UNIX experts who brought it to life. Read on and you'll see why. I warn
you right now that it's possible to write cryptic, unfathomable code in C in
no time at all, and that a C listing can resemble hieroglyphs. Hopefully I'll
persuade you not to follow suit should you take a liking to C.


	So, history lesson over, here goes.


C : Getting Started
-------------------

	C is a language with a well-defined set of standards. The ones I'll
be using here stem from the definitive C reference in the early days, a book
called 'The C Programming Language' by Brian Kernighan & Dennis Ritchie. In
referring to this book, I'll call it K&R for short (everyone else in the C
world does, so why not?). Where extra new features have been added to the
language after K&R, I'll give warnings accordingly.


	K&R introduces the novice to C by showing the now-famous program:


	main()
	{
		printf("hello, world\n");
	}


and this simple program says an awful lot about C. First, a C program is made
up of functions, which are to C what subroutines called with JSR/BSR are to
68000 assembler. Second, there is always a function called 'main', which cor-
responds to the main program.


	Now, it so happens that the above program mentions another function,
the 'printf' function. But it isn't defined anywhere! What is 'printf'? Well
the above program isn't actually complete. 'Printf' is a standard function
provided in a standard library (virtually all C libraries have them), and is
not that far removed from an Amiga library function. There just happens to be
an Exec function, RawDoFmt(), which acts a lot like printf(). How C programs
access these libraries I'll ignore for now, but don't forget this-I will come
to it later.


	Now, printf() takes a parameter in the example, the string "Hello,
World\n". Just what is the "\n" for? Well, it's a standard way of referring
to a carriage return or ASCII character 13. There's a list of such standard
references, but I'll leave them out for now.


C : A Program That Does Something
---------------------------------

	Right. Time to move on to a program that performs a real task, rather
than just cluttering up the screen with pithy messages. Sticking to K&R's now
established path, how about a program to convert temperature scales?


	The program I'll offer here isn't the ACTUAL one that K&R list in the
book, it's adapted somewhat, but here it is anyway:


		/* This is a comment!!! */

		main()

		/* display temperature scales, in both */

		/* celsius & fahrenheit */

		{

			int	first, last, step, counter ;

			float	fahr, celsius ;


			first = 0 ;		/* start at 0 deg C */

			last = 100 ;		/* end at 100 deg C */
 
			step = 10 ;		/* do in 10 deg steps */

			for ( counter = first; counter <= last; 

				counter = counter + step)

				{

				  celsius = (float) counter ;

				  fahr = ( (9.0/5.0) * celsius ) + 32.0 ;

				  printf(" %6.2f deg C = %6.2f deg F",

					celsius, fahr);

				}


Ok, now to decipher this awful looking mess. First, the character pairs /*
and */ are used to surround a comment. Everything between these character
groups is ignored by the compiler, and plays no part in the program except
to make it easier to understand.


	Next, some variables are declared. The two types of variable I have
used above are int (which are, surprise, surprise, integers), and float (one
of two types of floating-point numbers). On the Amiga, as well as the stand-
ard C variable types, there are others, such as UWORD. I'll deal with a good
selection of these shortly. The data types all tell the compiler what sort of
variable is being dealt with, and how much memory each needs (each type needs
a fixed amount of memory). The list of possible types (well, most of them) is
given below:


	Standard Types		Size		Notes
	--------------		----		-----

	char			8 bits		Character data

	int			16 bits		signed integer

	float			32 bits		single precision FP

	double			64 bits		double precision FP


	Amiga Types		Size		Notes
	-----------		----		-----

	BYTE			8 bits		Most of these types are

	UBYTE			8 bits		obvious. Those starting

	WORD			16 bits		with a 'U' are unsigned.

	UWORD			16 bits		This tells the compiler

	LONG			32 bits		to use different arithmetic

	ULONG			32 bits		code upon them.

	SHORT			16 bits

	USHORT			16 bits

	APTR			32 bits		Address Pointer

	BPTR			32 bits		Ptr to BCPL string

	BSTR			???		Used to denote BCPL string

	CPTR			32 bits		Ptr to normal C string

	CSTR			???		Denotes normal C string


Now, it just so happens that as well as int among the standard C types, there
are short variables (denoted, as expected, by 'short') and long variables too
which unfortunately are implementation dependent. Normally, an int variable
is the same size as the default machine word, a short is either the same size
or shorter, and a long normally equals two machine words. Also, before each
of these types, the word 'unsigned' can be prefixed, to allow definitions of
the sort:


		unsigned long x,y ;

		short a,b,c ;

		unsigned int f,g ;

		unsigned h,j ;  /* same as unsigned int! */


Most Amiga programs ignore the standard types altogether, and use only those
special types listed above. Oh, and a word of warning about floating-point
code. Amiga C compilers DO NOT USE THE MATHS LIBRARIES! They have their own
inbuilt support code for floating-point arithmetic, and if a programmer wants
to use the Amiga math libraries, special provision must be made!


	Should you wish to do something such as:


	int i;

	float f;

	i = f;


which normally would result in rude error messages from the compiler, it is
possible to convert from one data type to another, using a device called a
CAST. The above would be re-written:


	int i;

	float f;

	i = (int) f;


This forces the compiler to embed type-conversion code in the resulting pro-
gram at this point, to ensure that the data is copied into the required vari-
able and in the correct format. The C jargon for type-converting in this way
is called, needless to say, casting, and appears frequently in Amiga C code
in particular. WATCH FOR MISSING CASTS! These tend to occur when converting,
for example, an int up to a float or double, which the compiler can handle by
default. If assignments between odd variable types appear in a piece of code,
then remember to mentally insert the missing casts yourself (or better still,
edit the code where possible and make the casting explicit!).


	Before going on, one extra point. Arrays are defined using statements
of the form:


		int array[10] ;


defines an array of 10 integers, and


		char array1[20,20];


defines a 2D array of characters.


	And now, let's examine the actual code itself. The special characters


			{		}


are used to group together program statements. Every function must have at
least one pair, surrounding all of the program statements below the name of
the function. Of course, since they act as program brackets, just like the
brackets in arithmetic, they can be nested.


	In the program above, there is a flow control statement. This is the
FOR statement. Now this looks not unlike the BASIC FOR statement, but is much
more sophisticated and powerful. The FOR statement works like this:


		for ( i = a ; i <= b ; i = i + j )

			statement;


The first part of the FOR statement is the initialisation. It sets the value
of the variable being used as the counter, in this case i, and is performed
BEFORE entering the actual loop! The second part is the exit test, which con-
tains a test to perform at the START of each loop iteration. If the condition
is TRUE, then the statement below the FOR is executed. Here, we're executing
the statement every time that the test i<=b is true. The third and final part
determines how to affect the loop counter AFTER the 'statement' is executed,
and BEFORE the loop is re-executed.


	An assembler equivalent would be a loop of the form:


			moveq	#20,d0		;the 'i=a' part

	loop		cmp.w	d1,d0		;the 'i<=b' part

			bhi.s	exit		;here we're doing an
						;unsigned test


			...			;some code
						;corresponding to
						;'statement' above

			add.w	d2,d0		;the 'i=i+j' part

			bra.s	loop

	exit		...			;remainder of program


Now, back to those '{}' brackets. If I write code of the form:


		for ( i=a; i<=b; i=i+1)

			statement_1;

			statement_2;


then only statement_1 will form part of the loop. Statement_2 only gets exe-
cuted in this code once the FOR loop above it has finished. What if I want to
execute statement_1 and statement_2 in that sequence, over and over again? To
achieve this, write:


		for ( i=a; i<=b; i=i+1)

			{

				statement_1;

				statement_2;

			}


Whenever a group of statements is to be regarded as a whole unit, either to
be executed under a FOR or one of the other C control flow statements, then
simply surround them with '{}' brackets as above.


	For now, I shall ignore the (admittedly interesting) printf() func-
tion that appears in the example program, partly because most Amiga programs
don't bother with printf() at all:many use the built-in Exec RawDoFmt() func-
tion, and many more resort to far more sophisticated output via Intuition. So
I shall now move on to C's control statements.


C : Control Flow Statements
---------------------------


There are a number of useful control flow statements in C, which just happen
to have reasonable assembler equivalents in their simplest form. Considering
the FOR statement covered above, the equivalence is:



	for (i=a; i<=b; i=i+j )		move.w	a,d0	;the 'i=a'
							;part
	{				move.w	b,d1
		statements;		move.w	j,d2

	}			loop	cmp.w	d1,d0	;the 'i<=b'
					bhi.s	exit	;part

					...		;the various
							;statements

					add.w	d2,d0	;the 'i=i+j'
							;part

					bra.s	loop

				exit	...


or at least in the simplest case. This gives a good idea how FOR works, but
FOR is much more sophisticated than that. For example, to add up all of the
elements of a two-dimensional array might require two loops:


	for (i=1; i=20; i=i+1)

		for (j=1; j=20; j=j+1)

			total = total + a[i,j];


but FOR can do it in ONE statement (!!!) :


	for (i=1, j=1;

		i=20, j=20;

		i=i+1, j=j+1)

		total = total + a[i,j] ;


Don't try to work out how the compiler decodes this little lot! Just take it
as read that C compilers are sophisticated animals that can handle this sort
of shorthand construct, and take note also that C is littered with shorthand
code tricks of this kind, some of which are elegant and natural, others of
which are cryptic and designed by experts to make their code unreadable to a
novice (or a commercial competitor).


	Just to add to the fun, the FOR statement can have certain sections
omitted. For example:


		for ( ; i<1000; i=i+1)


takes whatever value i currently has without initialising it prior to enter-
ing the loop, and:


		for ( ; ; )


executes the loop forever! Be warned, shorthands of this sort abound in C, so
keep a look-out for them!


	Ok, having dealt with the FOR statement, now let's deal with the not
dissimilar WHILE statement. I give an example in C, plus a rough assembler
equivalent as in the FOR statement above, which looks like this:


	while (i<j)			...		;assume that
							;d0 = i, d1 = j
	{
		statements;	loop	cmp.w	d1,d0
					bcc.s	exit
	}
					...		;statements go						
							;here

					bra.s	loop

				exit	...


Again, the test condition in '()' brackets in the WHILE statement is perfor-
med BEFORE the loop proper is entered. WHILE is the premier choice when there
is no obvious loop counter, but WHILE and FOR are interchangeable in C. The
correspondence is:


		WHILE Version			FOR Version
		-------------			-----------

	statement_1;				for (statement_1;

	while (statement_2)				statement_2;

	{	other_statements;			statement_3)

		statement_3;			{ 

	}						other_statements;

						}


So even if there is no real 'loop counter' as such, it's still possible to
use a FOR loop, and it's possible to use a WHILE loop when there is one. And
if that isn't confusing enough, both loops can have all manner of shorthand
forms that either enhance or destroy readability.


	As well as loops with the exit condition tested for prior to entry,
there is a loop with the test conducted at the bottom, after at least one
iteration of the loop. This is the DO loop, which looks like this (again, I
provide a rough assembler equivalent):


	do				loop	...	;statements

		statements;			cmp.w	d1,d0	;i<j?
						bcs.s	loop	;back if so
	while (i<j) ;

						...		;else exit



Now that we can set up loops in C, it is about time to examine conditional
tests. C has an IF statement, which looks like this (I don't think that an
assembler equivalent is needed!):


	if (value == 10)		if (value == 10)

		statement_1;		{
						statement_set_1;
	else				}

		statement_2;		else

					{
						statement_set_2;
					}


In case you're wondering, the else clause is optional. This causes no end of
problems with nested IFs, incidentally. For example,


	if (n > 0)

		if (a < b)		/* the whole of this IF */
					/* with the else is treated */
			z = a ;		/* as a single statement tied */
					/* to the earlier IF above */
		else

			z = b ;


the else here is tied to the innermost of the nested IFs. What if you want it
to be tied to the outer IF? Well, you write:


	if (n > 0)

		{		/* this brace forces the whole of */
				/* the following IF to be treated */
		if (a < b)	/* as a unit unconnected with the */
				/* else clause below */
			z = a;

		}

	else

		z = b ;



Below I give a particularly nasty example. The idea is to test a collection
of array elements, and to perform some function if the elements are nonzero.
The number of elements is to be passed in n, but if n is zero, an error mes-
sage is to be reported. Watch the first example carefully - it doesn't work!


	/* this DOES NOT work */

	if (n>0)
	   for (i=0; i<n; i=i+1)
		if (s[i] > 0)
		  {
		    /* do whatever here */

		  }

	else

	   printf ("Error - n is zero\n" ) ;



	/* this DOES work. Note where the extra {} are !!!*/

	if (n>0)
	   {
	    for (i=0; i<n; i=i+1)
		if (s[i] > 0)
		  {
			/* do whatever here */
		  }
	   }

	else

	   printf ("Error - n is zero\n" )


Well, now you should be getting a feel for this. If not, don't worry, there
is more to come, and once the basics have been dealt with, I'll cover some of
the shorthand forms that will be encountered. But first, let's wind up the IF
statement and then move on to BREAK, SWITCH and friends.


	One construct found often in C programs is:


	if (condition_1)

		/* do this if condition 1 true */

	else if (condition_2)

		/* do this if condition 2 true */

	else if (condition_3)

		/* do this if condition 3 true */

	else

		/* do this if none of the above true */


The conditions are evaluated in the order in which they appear, and the next
condition in sequence is only encountered if the current condition being tes-
ted is false (and the corresponding IF falls through). An assembler equival-
ent would be (using specific tests to make the code sensible):


			cmp.w	#10,d0
			bne.s	not_10

			...		;do this if d0 = 10

			bra.s	exit

	not_10		cmp.w	#20,d0
			bne.s	not_20

			,,,		do this if d0 = 20

			bra.s	exit

	not_20		cmp.w	#30,d0
			bne.s	not_30

			...		do this if d0 = 30

			bra.s	exit

	not_30		...		do this if none of the above true

	exit		...		and then carry on...


So, there you have it. Multi-way IFs in C expressed the way that assembler
coders know and love them. But there's another way of handling this sort of
thing in C, and it's called the SWITCH statement.


	The SWITCH statement is a special multi-way decision maker. The above
assembler code, with different statements being executed upon the value of a
variable being 10, 20 or 30 would be represented as a C SWITCH statement as
follows:


		switch(n)

		{
			case 10 : /* do this if n = 10 */

			case 20 : /* do this if n = 20 */

			case 30 : /* do this if n = 30 */

			default : /* do this if none of the above */

		}


At each of the CASE statements, a block of statements can be executed with-
out the need for enclosing braces (unless they contain their own IFs, FORs
etc.). But each block of statements thus written must end with a BREAK state-
ment if you don't want to fall through to the next CASE. For example:


		switch(n)

		{
			case 10 : printf("Value is 10\n");
				  break;

			case 20 : printf("Value is 20\n");
				  break;

			case 30 : printf("Value is 30\n");
				  break;

			default : printf("Value is not 10, 20 or 30\n");
				  break;
		}


Now, some restrictions exist here. SWITCH can only take chars or ints (or in
the case of Amiga C, BYTE/UBYTE/SHORT/USHORT/WORD/UWORD types). The order in
which the CASEs appear (including the default) does not matter, although it
helps if they're in some logical order as above. But since some programs tend
to have bits tacked on as an afterthought during development, beware of the
appearance of odd CASEs after the default. It is considered good practice to
give the default case a BREAK to take account of this possibility, to prevent
the emergence of odd bugs during development, but watch out for deliberately
omitted BREAKs to force fall-through execution!


	And having been introduced to BREAK via SWITCH, now let's cover the
BREAK statement more fully. BREAK is used to exit from a particular FOR, DO,
WHILE or SWITCH to the next level up. An example of its use with a WHILE loop
is:


		while (i<20)
		{
		   if (a[i] < 0)
			break;
		   total = total + a[i];
		   i=i+1;
		}


This code computes the sum of the first n positive-valued elements of the ar-
ray a[], exiting upon finding the first negative element. Not the most excit-
ing example, but it serves its purpose.


	BREAK has a companion statement, CONTINUE. Sticking with our example
that adds up positive elements only, if we now want to add ALL of the posit-
ive elements of the array, we can write:


		while (i<20)
		{
		   if (a[i] < 0)
			continue;
		   total=total+a[i];
		   i=i+1;
		}


Here, the CONTINUE statement forces the flow of control to skip the state-
ments that follow the CONTINUE, and move on to the next loop iteration. This
comes in handy for handling all manner of situations where a piece of loop
code is to be executed only under certain conditions, where a conventional
IF statement would be cumbersome.


	One final statement. C has its very own GOTO statement, but since it
is infinitely abusable, most C texts ignore it. I provide it here solely for
cpompleteness.


	To use GOTO, one needs a label for the GOTO to refer to. Labels in
C consist of an identifier followed by a colon. A typical use of GOTO would
be something of the form:


		if (errcode > 0)
		   goto error;

		...		/* do something else here */


error:
		...		/* here is the error handler */


which is about the only use that I would recommend for it (and in any case it
is possible to write error handlers without it).


	That covers all of the control flow statements that can be used in a
C function, except to introduce the first class of C shorthands. In a state-
ment such as a WHILE, the expression in the brackets is precisely that - an
expression that evaluates to a particular value. For conditional expressions
such as those used in the above examples, C treats any expression that eval-
uates to zero as returning the boolean value false, and any expression that
evaluates to a nonzero value is treated as returning the value true. So, we
could have more complicated expressions in DO and WHILE expressions. In fact,
the FOR statement can also be treated thus!


	A standard function in C libraries for reading a character from the
keyboard is the getchar() function, and a typical shorthand encountered in a
program handling keyboard I/O looks like this:


	while ( (c=getchar()) != EOF)

	{
		/* rest of the code */
	}


The expression c=getchar() has been embedded inside the brackets of the WHILE
statement, which seems odd to those used to languages such as PASCAL. This is
perfectly allowable in C, and is a source both of elegant source code (which
compiles quicker and produces a more compact executable object code) and of
cryptic garbage in the wrong hands. Here, EOF is a constant meaning "End of
File", assigned to a standard value which is returned by getchar() whenever
the user has terminated the input. I shall deal with defining constants in C
in a separate section.


C : Functions In Depth
----------------------


I said early on that C programs are made up of functions. So far, only one of
the possible functions, main(), has been dealt with. How do we create a func-
tion?


	For instance, let us define a function to add up all of the elements
in an array. We want to pass to this function the array and the number of el-
ements in the array, and have the function return the sum.


	The resulting function looks like this:


	int sum_array(a,n)	/* return sum of array of n elements */

	int a[];			/* define what type of array to expect */

	int n;			/* type of n must also be defined */

	{
	   int sum, i;		/* define some local variables */

	   for (i=0; i<n; i++)	/* see later about i++ */
		sum = sum + a[i];	/* perform the computation */

	   return(sum);		/* return the result */

	}


This defines the function to be one that takes two parameters, and that re-
turns an int value. Of course, it is possible to define a function to return
a char, float or double etc., by changing the initial definition to:


	double sum_array(a,n)


or similar.


	Following the function header, which defines the number of paramet-
ers taken and the type of value returned, there is a list of declarations to
define the type of each parameter in the header. Then follows the code itself
plus any definitions for local variables needed by the function. These always
appear inside the '{}' braces defining the function's code, so as to avoid
confusion with the parameter definitions.


	Once that's done, the function code itself can be written inside the
braces, and can be as complex as you want.


	In the above definition, another shorthand has appeared. The short-
hand 'i++' is used in place of 'i=i+1'. In fact, there are four shorthands:


		i++ and ++i for i=i+1

		i-- and --i for i=i-1


A WORD OF WARNING ABOUT THESE! i++ and ++i both have the effect of adding 1
to the current value of i. However, if another piece of code accesses these
variables in this form, such as:


		a = i++;	/* this means 'copy i to a first, then */
			/* add one to it */

		b = ++j;	/* this means 'add 1 to j first, then */
			/* copy the resulting value to b */


then it is important to realise in what order the computations are performed.
In the first example, the value of i is copied to a, then i is incremented.
In the second example, the value of j is incremented first, then copied to b.
Similar caveats apply to --i and i--. The ++ and -- operators (increment and
decrement) can be applied to any variable, for example:


		for (i=0; i<20; i++)

		    (a[i])++;


which increments each element of the array a[] in turn.


	Back to functions. I stated that it was possible to define a funct-
ion that returned a non-int value (int is the default on most C compilers).
Well, this is true. But as well as writing a definition such as:


	double sin(x)

	double x;

	{

	   ?* code goes here */

	}


when it cones to using the result in main(), main() must be told that the
function sin() returns a double. So, in main(), we have a definition of the
form:


	main()

	{

	   double angle, result, sin()

	   /* rest of code follows this... */

	}


to tell main() that sin() is a function that returns a double type value. The
same must apply to ALL functions returning a non-int value.


	One final point. There are numerous occasions when it is appropriate
to write a function that returns NO value at all. In K&R, this was handled by
simply defining a function without a type specifier, and simply neglecting to
end the function with a return() statement. A new standard has since arisen,
based upon the ANSI C standard, where such functions need to be explicitly
defined. The standard requires such a function to be defined as follows:


	void noreturnvalue(x)

	int x;

	{

	   /* here do something with x */

	   /* but there is NO return() */

	}


Thus C can now have functions that behave like PASCAL procedures, or 68000
subroutines without return values. The special type VOID is used to declare
such a function. Note that if main() is to access such a function, then it is
necessary to declare its existence as such when defining the variables that
main() is using, as in:


	main()

	{
	    int x,y;		/* some variables */

	    void SetAPen(), SetBPen();

	    /* rest of program follows */

	}


The use of VOID is covered more fully in any text covering the ANSI C stan-
dard. Recently a new version of K&R came into existence which covered the new
ANSI C extensions to the original K&R standard, and this new extended stand-
ard has found widespread use. For example, Lattice C and ZorTech C compilers
both implement the ANSI C extensions.


C : Variable Scope
------------------


The above section on functions makes it now imperative to talk about variable
scope. In other words, what functions can refer to what variables, and how a
given variable behaves.


	In the definition of the function sum_array() above, two local vari-
ables were defined, namely i and sum. As local variables, they can be access-
ed only by sum_array(). They are created whenever sum_array() is called, and
exist solely for the duration of sum_array()'s execution time. Upon returning
the function value, those variables cease to exist within the machine's mem-
ory. They are deallocated once the function is no longer in use.


	The same is true of all variables defined in this way, even within
main() itself. They are created and allocated memory when the given function
is executed, and once the given function has ceased execution, that memory is
freed for use by other functions.


	However, not all variables behave like this. If a variable definit-
ion occurs outside of ANY function, then those variables are known (as may be
surmised) as external variables. External variables again are allocated and
deallocated, but can be accessed by any function.


	Complications set in when considering large C programs occupying two
or more source files. Let the lines:


	int sp;

	double val[1000];


appear in source file 1, outside any functions. This DEFINES the variables,
allocates storage, and serves as the declaration too for source file 1. Only
one DEFINITION of the variables (namely this one) must exist in the source
files.


	But let us assume that source file 2 contains functions wishing to
access these variables. Source file 2 must then contain DECLARATIONS of the
form:

	extern int sp;

	extern double val[];


to allow its functions to access them. These extern statements DECLARE to the
functions in source file 2 that sp is an int, and val is an array of double
variables, but that their size is determined elsewhere and that storage has
been set up for them elsewhere too. Source file 1 (in this example) is the
source responsible for the creation of sp and val[] and hence contains the
definition. An external variable referenced via an extern declaration can be
referenced by any function, provided that the function has access to a suit-
able extern declaration (i.e., one exists in the same source file).


	There is a third class of variables. Static variables exist too, and
can be either internal or external. For example:


	int function(f)

	int f;

	{

	   static int i;

	   /* rest of code */

	}


This defines i to be a variable known only to function f, but which remains
in existence throughout the life of the program, instead of ceasing to exist
once f() has finished executing. Thus they provide a means of granting perma-
nent yet PRIVATE storage to a function that needs it for some reason.


	External static variables, defined using lines such as:


	static char buf[2000];

	static int bufp = 0 ;


provide a way of granting permanent storage to a collection of functions in a
specific source file,  while restricting access to those functions only. The
word 'static' in C confers not only permanence of existence, but privacy to
one function or a collection of functions. In fact, the degree of privacy is
so complete that the same variable names can be used elsewhere in other files
without conflict. This provides C with a limited form of modularity, as found
in the MODULA-2 language.


	And to round off the variable scope section, C betrays its origins
as an alternative to assembler by means of register variables! Yes, C has the
ability to tell the compiler to place a given variable (especially if it is
heavily accessed) in a machine register for speed. A declaration in main() or
another function of the form:


		register ULONG idcmp;	/* specially for the Amiga */


tells the compiler to place the variable idcmp in a processor register on any
machine for which such a compiler exists. On the Amiga, the above declaration
may instruct the compiler to select one of the 68000 data registers d0-d7 for
the variable idcmp, but there is no guarantee that this is the case. If there
is a spare processor register available, then idcmp will be assigned to it.


C : Defining Constants Etc
--------------------------


Just as 68000 assembler has an EQU directive to assign labels to constants, C
has the ability to assign labels to strings. The C equivalent of EQU is known
as the #define substitution. For example:


#define	BUFSIZE	2000

#define	ZEROCHAR	'0'


There is a difference between the C #define and assembler's EQU. #define has
the effect of setting, for example, the label 'BUFSIZE' to refer not to the
value 2000, but to the STRING '2000'. This is used by the C preprocessor (a
kind of 'boy scout' program that prepares C source for the compiler proper,
often the first part of the C compiler itself) which looks for all occurren-
ces of 'BUFSIZE' in the program, and substitutes the string '2000' for 'BUF-
SIZE' wherever it is found. The C source thus edited is passed to the compi-
ler proper.


	Better still, complicated macros can be defined, that take parame-
ters, just like functions, for example:


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


This has been defined as a quick way of writing x*x in such a way that it can
be invoked in lines such as:


		p = square(a+b)


Note that the original #define macro has brackets around each x. This is to
ensure that when the macro is invoked, and the substitution is performed by
the preprocessor, the resulting line appears as:


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


Without the brackets in the #define, the result would be:


		p = a+b*a+b


which would have the effect of computing:


		p = a + (b*a) + b


upon execution - NOT what is wanted.


This wraps up the first C tutorial for ACC members. Watch this space for the
second and subsequent tutorials, and as always,



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



			Dave.







