


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


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


				Part 2
				======


Contents:
---------

Introduction

C : Variable Scope Extended

C : Arrays And Pointers

C : Pointers And Functions

C : Address Arithmetic

C : Character Pointers

C : Pointer Type

C : Pointers To Pointers


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

	This tutorial file assumes that the reader has already read and di-
gested the guts of the first tutorial file, which is not an unreasonable as-
sumption.


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


In the first tutorial file, I managed to get as far as covering the scope of
a variable in C, in other words, what sort of variable it is, what can refer
to it and use it, and what source files it is known to and under what condi-
tions. I now plan to move on to cover the way in which C handles those argu-
ments passed to its functions, which comes partly under the heading of vari-
able scope.


	C functions that have simple variables passed to them, such as the
function:


	double sin(x)		/* approximation to sin(x) */

	double x;

	{
	   double p1, p2, p3, p4;

	   p1 = x;		/* this lot computes the */

	   p2 = x*x;		/* formula x-(x^3/6)+(x^5/24) */

	   p3 = (p1 * p2) / 6;

	   p4 = (p3 * p2) / 4;

	   p2 = p1 - p3 + p4 ;

	   return(p2);

	}

do NOT have direct access to the variable x. The mechanism by which simple
variables (as opposed to arrays and other variable types to be dealt with in
a later section) are passed is known as 'call by value'. This means that the
value of the variable in a function call such as:


		s = sin(angle);


is given to the function, in the form of its OWN PRIVATE COPY. In the above
call, the sin() function cannot have any effect upon the variable 'angle' at
all. Instead, a private copy of 'angle' is made for the exclusive use of the
function, and it is that private copy that the function manipulates. Since
this private copy is like other variables in the function, and ceases to ex-
ist once the function has completed its task, there is no way that the func-
tion can affect the actual value of variables passed to it.


	But what if this is what you want? Well, for now, the only way that
is available is to use external static variables, covered in the first tutor-
ial. But other ways do exist, and it is with these in mind (and with the mat-
ter of passing arrays to functions, which is also possible) that I shall now
move on to cover possibly the most important aspect of C : the existence of
array and pointer variables.


C : Arrays And Pointers
-----------------------


C has the capacity to handle arrays. Simple definitions of an array have al-
ready been encountered in the first tutorial, for example:


	main()

	{

		int a[30];

		/* rest of 'main' from here on */

	}


This defines an array of 30 integers. But in C, every array begins with the
element number zero, so the above array consists of the elements:


		a[0], a[1], a[2], ... a[28], a[29].


This is important to bear in mind for the future, especially when I describe
the operation of pointers, which are used extensively to handle arrays in C
programs. This I shall now do, because pointers and arrays are strongly tied
together in C.


	What is a pointer? Well, assembler coders will know pointers as being
addresses of data in memory, usually stored in address registers on the 68000
and accessed via instructions such as:


		move.l	(a0),d0


C pointers are similar in operation, but not identical. It is true that when
a compiler takes C pointer code, it generates instructions of the above type,
but there are restrictions. Assembler coders can point the address registers
wherever they like, without regard to the data type being pointed to (barring
the generation of bus errors, etc). C pointers are usually coupled to data of
a specific type, and strange things can happen if, for example, a pointer to
an int variable is declared, and then pointed to a double variable.


	So, how do we create a C pointer? The small code example below will
serve to illustrate this, and some elementary details of pointer operation:


	main()

	{
	   int x, y;	/* declares two normal int variables */

	   int *px;	/* declares px to be a pointer to an int */

	   px = &x;	/* sets px to point to x */

	   y = *px;	/* sets y to the value of what px is pointing at */

	   y = x;	/* which is the same as this! */

	}


First, two symbols. The symbol & means 'the address of' in C, and the equiv-
alence between C and assembler would be something like this:


		int x,y ;			x	dc.w	0
						y	dc.w	0
		int *px;			px	dc.l	0

		px = &x;			lea	x,a0
						move.l	a0,px


Not too bad, is it? The second symbol * (NOT to be confused with multiplica-
tion: I shall show how to avoid this below) means 'what is being pointed to'.
The C and assembler equivalents would be something like:


		int x,y ;			x	dc.w	0
						y	dc.w	0
		int *px;			px	dc.l	0

		y = *px;			move.l	px,a0
						move.w	(a0),d0

		y = px;		<-CARE!->	move.l	px,a0
						move.l	a0,d0


Notice that *px refers to what px points to. The variable px is the pointer
proper, and *px is whatever it points to. Study the C to Assembler correspon-
dence given above-it is important that this is mastered when deciphering a C
program such as one of the Amiga ROM Kernel Manual examples, and converting
it to assembler.


	Ok, I said that pointers and arrays were strongly tied together. This
is true. Let me demonstrate:


	main()

	{
	   int a[10];		/* define an array of 10 ints */

	   int x;		/* define a simple int variable */

	   int *pa;		/* define a pointer to an int */

	   pa = &a[0];		/* makes pa point to a[0] */

	   x = *pa;		/* get the value of a[0] */

	   x=a[0];		/* previous two lines do this */

	   pa = &a[0];		/* get pointer to a[0] */

	   pa = a;		/* so does this!!! */

	}


Now, the above reveals the strong linkage. Arrays are actually referred to by
pointers internally:the compiler generates pointer code to reference arrays.
So the following is valid:


	   pa = &a[0];

	   pa = a;		/* same as above */

	   x = *pa		/* is the value of a[0] */

	   x = *(pa+1)		/* is the value of a[1] */

	   x = *(pa+j)		/* is the value of a[j]

	   x = *(a+j)		/* so is this!!! */

	   x = *(pa+j)		/* the value of a[j] */

	   x = pa[j]		/* so is this!!! */


With a few restrictions, it is possible to exchange pointer and array refer-
ences as above. In fact, when evaluating a[j], C converts it to *(a+j) immed-
iately. The two forms are completely equivalent. The sole restriction comes
from the fact that in the above example, a is a constant label and pa is a
variable, so while constructs of the form:


			pa = a;

			pa++;


are allowed, the following constructs:


			a = pa;

			p = &a;


are NOT allowed when a refers to an array as defined above.


	Pointer variables such as *px can appear in expressions just as can
ordinary variables. So the following are valid:


	int x, y;

	int *px;

	double d;

	px = &x;		/* get address of x */

	y = *px + 1;		/* get value, add 1 and store */

	y = x + 1;		/* which is the same as this */

	d = sqrt ( (double) *px); /* note the cast:sqrt requires a */
				  /* double argument */


	*px = 0;		/* same as x=0; */

	*px = *px + 1;		/* same as x = x + 1 */

	(*px)++;		/* so is this!!! */

	*px++;			/* this is actually *(px++); */
				/* and increments the pointer address! */


Now, having introduced the ++ and -- operators in the first C tutorial, which
increment and decrement a variable respectively, it is time to consider just
how they apply to pointers.


	Well, the principal matter to consider here is that since C pointers
are coupled to data of particular types, then the size of the data type must
be cponsidered whenever a construct of the form:


		*px++


is encountered. If px points to an array of Amiga ULONGs, for example, then
the result of the above operation will be the addition of 4 (since a ULONG is
4 bytes) to the address contained in px. So, the assembler correspondence is
something like:


		*px++			move.l	px,a0
					tst.l	(a0)+


So, just as the 68000 adjusts the address register contents in instructions
using (a0)+ or -(a0) addressing modes according to the size of the operand
(be it byte, word or long), so C adjusts pointers in an analogous manner. It
is also possible to use:


		*--px


to reference a previous data element, similar to -(a0) in assembler.
	

C : Pointers And Functions
--------------------------


	Since it is possible to pass simple variables to C functions, it is
only reasonable to expect to be able to pass pointer variables to C functions
too. In fact, this is how C allows arrays to be passed (examples of this have
appeared in tutorial 1), courtesy of the array/pointer correspondences shown
in the above section.


	Since simple variable passing does NOT allow a C function to affect
the variables passed to it, but only allows manipulation of private copies of
the said simple variables, one might think that the same applies to pointers.
Well, a copy of the POINTER is passed, true, but now the function has access
to what is POINTED TO. This is the well-behaved way of allowing a C function
to affect variables passed to it.


	Let me take an example directly from K&R (the definitive C book), the
function swap(a,b). The idea is to allow this function to exchange two varia-
bles (for example, during a sort algorithm). K&R gives the following:


	/* swap function, WRONG version */

	swap(x,y)

	int x,y;

	{
	   int temp;

	   temp = x;		/* this is OK */

	   x = y;		/* but this won't work */

	   y = temp;		/* nor will this */

	}


	/* swap function, RIGHT version */

	swap(px,py)

	int *px,*py;		/* now we're using pointers */

	{
	   int temp;

	   temp = *px;		/* get value that px points to */

	   *px = *py;		/* this is the same as x = y, but works */

	   *py = temp;		/* and this works too */

	}


One major difference that needs to be covered here is how a function taking
pointer arguments needs to be called. The version of swap() above should NOT
be called using:


	swap(a,b);


but using:


	swap(&a,&b)


because it expects the ADDRESSES of the variables concerned.


	Needless to say, if the function uses arrays, these details need not
be considered so carefully, because C automatically handles its array passing
as if it were pointer passing, and deals with the mechanics in a reasonably
transparent fashion. But there are situations where pointers are explicitly
used, because 1) they are more appropriate, and 2) they are more elegant. In
these situations, keep the above in mind.


	Pointers therefore allow a function to return more than one value, by
changing the values of the variables whose addresses it receives. So a func-
tion can be defined as a VOID function (no explicit return parameter in ANSI
C) but affect as many variables as are passed to the function.


C : Address Arithmetic
----------------------


	C has as one of its strengths, a suitably consistent handling of the
arithmetic performed upon address operands. There is indeed some correspond-
ence between the K&R standard (and the ANSI extended standard) and the way in
which the 68000 handles address arithmetic, but the correspondence is NOT in
any way absolute.


	First, pointers may be compared. It is legal, given two pointer vari-
ables px and py, to write


		if (px < py)

		   statement_1;

		else

		   statement_2;


just as it is legal to use CMPA in assembler (C compilers for 68000 systems
often generate the special address arithmetic instructions directly in the
resulting native code). One caveat:the above is valid if px and py point to
elements of the same array. If they point to different arrays, however, gar-
bage could result!


	Second, constants may be added to or subtracted from pointers. So it
is legal to perform:


		px = px + 1;

		px = px - n;


However, since pointers are tied to specific data types, the above statements
do NOT add 1 or subtract n in all cases. The constant added or subtracted is
pre-multiplied by the size of the data object pointed to BEFORE the addition
or subtraction is performed. So, if an Amiga C program contains a reference
such as:


		px = px + 1;


where px is a pointer to a ULONG, then px will actually have the value 4 ad-
ded on to it (4 bytes being the size of a ULONG).


	Since the size of a data object is important, it is useful to know
how much memory a given object occupies. C supplies a standard library func-
tion, called sizeof(), which returns the size in bytes (or on mainframe in-
stallations, the size in char type units) of either the supplied variable or
a named type such as int. So it is possible to find out how much storage a
given variable occupies by requesting:


		x = sizeof(var);


or:


		x = sizeof(double);


However, pointer arithmetic AUTOMATICALLY multiplies any constants added to
or subtracted from a pointer by the value of the sizeof() function for the
given data type. So sizeof() is NOT needed for pointer arithmetic by the pro-
grammer (but it may be needed for other purposes).


	As well as adding an subtracting a constant, one pointer can be sub-
tracted from another. Again, the result of the subtraction is NOT the actual
numerical difference between the address values, rather the difference ex-
pressed in units of the data type pointed to. So if px and py are pointers to
an array of floats, then:


			py - px


is the number of float elements between py and px.


	However, it is illegal to ADD two pointers together in C (at least in
K&R C, I am not precisely sure about ANSI C) and it is illegal to multiply or
divide them, shift or mask them, or add values of the types float or double
to them. This complies with the legal range of operations that can be perfor-
med upon addresses in 68000 assembler, with the exception of adding pointers.
It is possible (though not usually very useful) to add two pointers together
in 68000 assembler, as in:


			adda.l	a1,a0


C programs forbid this for actual pointers. Of course, a C compiler may form
instructions of this kind if the source operand happens not to be a C pointer
but is stored in an address register because there is nowhere else for it. I
do stress, however, that the restrictions C places upon address arithmetic in
programs reflects strongly the actual processor restrictions of the 68000.


C : Character Pointers
----------------------


	C treats all strings as arrays of characters. A standard C string is
a string of ASCII characters (or, if you're using an IBM mainframe, which is
unlikely for ACC members, EBCDIC characters) terminated with a zero byte. If
a reference is needed to this string, either the string can be treated as an
array, and defined as such, or else a pointer can be assigned to it (which in
C amounts to the same thing!).


	Strings can either be initialised by the program, by directly filling
an array of chars with character bytes, or by embedding string constants in a
program such as:


		printf("Hello, World\n");


When such string constants are present in a C program, and used in functions
such as printf(), the compiler actually embeds the string in a separate data
area in the resulting machine code, and embeds instructions to pass to the
function (such as printf() above) a pointer to that string.


	Another way in which a string can be defined is by defining a pointer
to a char variable:


		char *message;


and then assigning a string to it, as in:


		message = "Hello, World\n";


Strings can be handled in a number of ways. Again, I shall refer to K&R and
use the example given there of the strcpy() function, to copy the contents
of one string to another. First, there is the array version:


	void strcpy(s,t)		/* copy string s to string t */

	char s[], t[];

	{
		int i;

		for ( i=0; ((t[i] = s[i]) != '\0'); i++)
			;

	}


A quick explanation of the shorthand used. In the FOR loop, i is set to 0 be-
fore entry. Then, at the start, the code:


		((t[i] = s[i]) != '\0')


has several effects. First, each element s[i] is copied to each element t[i],
and the result of this assignment is compared with an ASCII NULL (the '\0' in
the above code stands for a NULL character) and the entire expression returns
boolean true if that result is not equal to NULL. Once the test succeeds, the
statement following the loop is executed (which in this case is simply a ';'
statement to give the FOR loop something to be coupled to:all of the actual
computing work is done inside the complex body of the FOR!) and the counter
is incremented (the i++ part of the FOR). Take note of the above, which is a
modified version of the K&R strcpy() function:it is a prime example of the
use of shorthands in C, and code like it can be expected to crop up in many
different places!


	Now let's look at the pointer version. It uses a WHILE instead of a
FOR loop, and relies upon a different shorthand (yes, a shorthand again!):


	void strcpy(s,t)		/* copy string s to t */

	char *s, *t;			/* uses char pointers */

	{
	    while (*t++ = *s++ )
		;

	}


By way of comparison, here's the 68000 assembler equivalent:


	strcpy	move.b	(a0)+,(a1)+
		bne.s	strcpy
		rts


A quick explanation. The (*s++ = *t++) does an awful lot in one line, just as
MOVE.B (A0)+,(A1)+ does a lot in one line of assembler. In fact, the two are
almost directly equivalent. They both take the byte pointed to by the source
pointer, and copy it to the byte pointed to by the destination pointer, and
increment the two pointers to point to the next bytes in one go! Not only is
all this performed, but the entire expression has a value, just as the MOVE.B
has an effect upon the condition codes. The WHILE loop uses that value to de-
cide when to terminate, namely when the value is zero (comparable to the BNE
branch in the assembler equivalent).


	Now because the WHILE, just like the FOR, needs a statement below it,
a dummy null statement ';' is used for this purpose. All of the actual work
is done inside the body of the WHILE. I warned about the cryptic nature of C
programs in tutorial 1, and this serves as an example.


C : Pointer Type
----------------


	One question remains. What data type are pointers themselves? Well,
on the Amiga, all pointers must be address-register compatible (the special
types APTR and CPTR are provided for this), whereas on older systems such as
Z80 CP/M computers and PDP-11 minicomputers, pointers were directly compati-
ble with 16-bit int variables.


	This leads to a problem when using software derived from these older
sources. Older C programmers had a nasty habit of writing functions that were
cavalier in their treatment of pointers, particularly if the functions were
returning pointer values. For example, the CORRECT way of defining functions
that return pointers is (this returns a pointer to a char):


	char *function(s)	/* function returning pointer to char */

	char *s;		/* takes a pointer to char as an argument */

	{
	   /* rest of function goes here */
	}


Because pointers on older machines were compatible with ints, and int was the
default type of all C objects (variables, function return parameters etc), it
was common to see the char * declarations missing! Miss them out on Amiga C &
the result is a visit from the Guru, if the compiler is stupid enough to let
it through (most Amiga C compilers WON'T let this sort of code through with-
out generating rude messages, though).


C : Pointers To Pointers
------------------------


	And yes, it's possible to have pointers to pointers! This is usually
done to implement two-dimensional arrays, so that if a 2D array of chars is
defined, the two definitions:


		char argv[][];


and:


		char **argv;


are almost directly equivalent. In fact, a third equivalence exists here, the
declaration:


		char *argv[];


So once again, the interchangeability of arrays and pointers in C is manifest
in a single line of code.


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



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



			Dave.







