


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


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


				Part 4
				======


Contents:
---------

Introduction

C : The ? statement

C : A Little More On Functions

C : Bit Fields And Constants

C : The Lint Semantic Debugger

C : Some Notes On 680x0 C

C : Operators And Precedence

C : Advanced Odds And Ends

C : Anachronisms


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

	Having dealt with the main features to be encountered when decipher-
ing the Amiga C examples in the Amiga RKM and other sources, I now devote the
fourth tutorial to some more advanced matters, and to one or two features of
C that were (sadly) omitted in the previous three tutorials.


	Needless to say, this file makes far more sense if you already have
the first three C tutorials in this series. For those who haven't, or those
sad souls who have lost their copies, then these can be obtained via Mark or
myself at the usual addresses.


	A word of warning. Some of the material contained in the 'Advanced
Odds And Ends' section is not called advanced for nothing. Some of it is of
use to assembler coders directly, the rest is optional. Either completely ig-
nore this section (which it is perfectly safe to do if you wish to) or ignore
those parts of it that are definitely beyond your ken. However, if you like a
challenge (or alternatively have a masochistic bent) dive in and enjoy (?).
The rest of the tutorial is intended to be a good deal easier, even the bit
about operator precedence, which is NEEDED if your intention is to write any
operational C code, so browse through the other sections as desired.


C: The ? Statement
------------------

	One sad omission from the previous series of tutorials concerns ano-
ther control statement. Well, it's actually more than this, not that anyone
would suspect it upon seeing an example in a file, since it consists primar-
ily of the '?' character.


	The ? statement is actually a control statement AND an assignment
statement in one. The result of a ? statement is a value of some sort. How is
this value computed? I give an example:


		z = (a > b) ? a : b ;


This is how it works. The statement has three parts, a conditional expression
(in this example, the expression a > b) which MUST be contained within paren-
theses to separate it from any assignment operators (as in this example), and
two assignment expressions. The ? and : characters are used to separate these
three parts.


	The conditional expression is evaluated. This returns a boolean value
(recap:in C, FALSE is zero, TRUE is any nonzero value), used to determine the
final value returned by the whole ? statement. If the conditional expression
returns TRUE, then the above ? statement returns the value of a. If the con-
ditional expression returns FALSE, then the above example returns the value
of b. This value is assigned to z in the above example.


	A quick analysis of the above example reveals that this is a neat and
compact way of writing a MAX(a,b) function, which returns the largest of its
two arguments. But other uses are open for this statement, and one use that I
am going to discourage strongly is that of writing cryptic source code. This
statement lends itself to this anti-social practice to an alarming degree, &
when combined with pointer assignments produces what one critic of bad coding
practice described as 'write-only code'.


	Note that only one of the assignment statements (i.e., the values of
a and b in the above example) is actually evaluated. A correspondence between
this statement and assembler would be:


	z = (a > b) ? a : b ;		move.w	a,d0		;the a > b
					cmp.w	b,d0		;part
					bhi.s	point1

					move.w	b,d0		;the : b
					rts			;part

				point1	move.w	a,d0		;the ? a
					rts			;part


Note that the extra assignment in the assembler code above is not needed. The
value of a already exists within the d0 register, but the extra assignment in
the assembler code demonstrates the correspondence between the parts of the
statement as per the comments.


C : A Little More On Functions
------------------------------

	In the previous three tutorials, one point I omitted when discussing
functions was the mechanics of calling one function within another. If you
have a function such as:


	int atoi(s)		/* convert string to int */

	UBYTE *s;

	{
		double atof();

		return( (int) atof(s) );

	}


where atof() is a function that already exists somewhere else, then in order
for atoi() to use atof() correctly within its own body, a declaration that a
function atof() exists is needed within atoi(), along with the data type of
the return parameter (here, a double). The function atof() also needs a sim-
ilar declaration in main(), otherwise the compiler could get confused. Both
the function definition itself AND the calling routine must agree upon how
atof() is used. If the definition of atof() and atoi() (or main(), for that
matter) are in the same source file, then the compiler will pick up the error
and warn the porgrammer. If, as could be the case, atof() and atoi() (or even
atof() and main()) are in different files, and compiled separately (say that
atof() is compiled into a linkable object code library) then the mismatch
would NOT be detected by the compiler alone. The Lint semantic dbugger (see
below) WILL trap such errors, but without access to Lint, the programmer may
conceivably compile a program with an inconsistent call to atof(), where the
atof() function returns a double, which the caller then treats as an int (or
a UWORD on the Amiga). The result:meaningless garbage values cropping up in
a program.



C : Bit Fields And Constants
----------------------------

	To recap, a C structure is defined as (for example):


	struct treenode {

			UBYTE *treetext;

			UWORD count;

			struct treenode *left;

			struct treenode *right;

			} ;


Structure definitions can also be used to assign bit fields within a machine
word. For example:


	struct flags {

			unsigned quitting : 1;

			unsigned in_requester : 1;

			unsigned in_menu : 1;

			} ;


Here, the constant after the : specifies the number of bits that the field
thus defined occupies within the word. The usual alternative of defining a
series of masks such as:


	#define QUITTING	0x01		/* the idiom 0x always */

	#define IN_REQUESTER	0x02		/* prefixes a hexadecimal */

	#define IN_MENU		0x04		/* number */


and then using such idioms as:


	flags = (flags | IN_MENU) ;

	flags = (flags & ~(IN_MENU | IN_REQUESTER);


can be changed to:


	flags.in_menu = 1;

	flags.in_menu = flags.in_requester = 0 ;


Notice the multiple assignment in the last example. In C, an assignment such
as:


	x = y = z = 0 ;


is perfectly valid, and is often used to handle setting and clearing of mul-
tiple bit fields as defined above.


	WARNING. Exactly HOW a collection of bit fields is assigned to a real
machine word is heavily compiler-dependent. DO NOT assume that the compiler
will always start with bit 0 and work upwards - some compilers could begin a
bit field assignment with bit 15 (for a UWORD sized structure) and work down-
wards.


	Having covered bit fields, not it is time to deal with how C speci-
fies various constants. The following list is valid for K&R C (see your com-
piler documentation for any extra extensions):


	1) Character constants. Used in assignments to char variables,
	   except \" which is used to represent the " character within
	   a string, as in "\"Hello, World\"".


		Character		Symbol
		---------		------

		Newline (ASCII $0A)	\n

		TAB (ASCII $09)		\t

		Backspace (ASCII $08)	\b

		Return (ASCII $0D)	\r

		ASCII $0C		\f

		\			\\

		'			\'

		"			\"

		ASCII NULL		\0

		Bit pattern		\ddd (ddd is an OCTAL or base
						8 number!)


	2) Numeric constant types.


		1234			Decimal constant

		0377			Octal (base 8) constant
					if begins with 0.

		0xff, 0XFF		Hex constant

		1234			Integer constant

		1234l, 1234L		Long constant (e.g., a
					ULONG).

		12.0			Any constant containing a
					decimal point is a DOUBLE
					constant (care if assigning
					to a FLOAT!).

Should your compiler allow other extended constant types (for example, some
C compilers allow binary constants of the form 0@11101010 or similar), check
the associated documentation for further details. One common extension that
is allowed on modern compilers (check if your compiler does before using it,
though!) is the use of hex constants to specify ASCII characters in conjunc-
tion with the '\' character, as in:


	#define		CR	\0x0D	/* same as \r */

	#define		NL	\0x0A	/* same as \n */


instead of being restricted to octal constants (how many Amiga coders have
even heard of octal, let alone used it?).


C : The Lint Semantic Debugger
------------------------------

	C development systems on older computers (typically PDP-11 minis and
DEC mainframes) included a program that might be available within the Amiga C
environment. This program is called Lint, purportedly because it 'picked bits
of fluff' off C programs. This program performs the following functions:


	1) Reporting type clashes within expressions;

	2) Checking whether the value of an expression
	   depends upon the order of evaluation or not;

	3) Checking for garbage function return values
	   for VOID functions not declared as such (or
	   undeclared on code written prior to the VOID
	   standard being adopted);

	4) Missing pointer type declarations in older C
	   code involving functions returning pointers;

	5) Checking the validity of preprocessor directives
	   such as #define etc;

	6) Ensuring that cross-references across different
	   source files match consistently (e.g., static &
	   external variables);

	7) Ensuring that function calls to a linker library
	   are consistent with the definition of the funct-
	   ion (so that your main() doesn't treat a return
	   value as a UWORD when it should regard it as a
	   double, for example);

	8) Catching anachronistic constructions left over
	   from earlier versions of the language (see the
	   section on 'Anachronisms' below).


This program existed as a separate entity in older C development environments
such as the DEC PDP-11, but may have been included within the compiler itself
on modern C compilers such as those for the Amiga. If it exists as a separate
entity, USE IT - almost all of the really stupid errors are found by Lint, as
well as some of the more obscure ones (see 4,6,7 and 8 above).


C : Some Notes On 680x0 C
-------------------------

	Those who have studied the correspondence between C and assembler on
680x0 family chips thus far may have noticed that C code compiles into neat,
compact 680x0 code. This is due to one of the principles adopted by Motorola
when the 680x0 family was conceived.


	Apart from producing a chip with an almost mathematically regular in-
struction set, one of the requirements was for an instruction set that lended
itself to compact compiler construction for high-level languages. C was chos-
en as the model for this, hence the high degree of correspondence between C
code statements of one line and assembler code statements of a few lines only
(in some fortuitous cases, a single line of assembler).


	In fact, the standard C compiler for many systems compiles not into
machine code direct, but into assembler. The resulting assembler file (which
is often littered with absolute data references to support external variables
and static data structures) is then assembled in the usual manner.


	Despite the efforts made by Motorola to produce an instruction set of
exquisite nature, many C compilers still do not use it to the full. In parti-
cular, the addressing modes such as offset(An,Xn) which lend themselves to 2D
array usage so well tend to be neglected by C compilers. The situation is not
much better on 68020/30/40 compilers - the bit field instructions such as:


		bfffo	d0{16:5},d1	;68020 instruction!


which lend themselves to bit field assignment as in the above section cover-
ing bit field manipulation seldom find use.


	However, the existence of 17 CPU registers on a 68000 (and more on
the 68010 onwards, although these are for specialised use only) means that a
variable declaration of the form:


	register ULONG idcmp;


stands a good chance of actually being assigned to a CPU register. On older
computers with fewer CPU registers (and particularly on older microcomputers
based upon chips such as the 6502, Z80 and 6809) the registers were allocated
by the compiler for specific purposes within the compiled code, and variable
declarations such as the above were treated as though the register specifica-
tion did not exist (because there weren't enough registers to go around). In
this case, the variable usually ended up being located in memory somewhere,
along with all of the other non-register variables. C compilers for 68000 or
680x0 systems sometimes find themselves with the luxury of a spare register
for such assignment during compilation, and so unless several register dec-
larations are made (using up all of the spare CPU registers) a register dec-
laration stands a good chance of ending up being obeyed.


	One quick word of warning while I think about it:whatever you do, if
you write C code for a quick fix while working out the elegant assembler ver-
sion, DON'T try operations of the form:


		x = &y;


where y is a register variable! C compilers spit out some of their rudest er-
ror messages when they encounter mistakes of this sort (and be warned:it's an
easy mistake to make if converting code from another machine to the Amiga, so
always keep track of register declarations and DON'T try taking the address
of or performing invalid pointer operations upon a register variable!).


	The primary cost to be paid within C lies with the use of stack fra-
mes for parameter passing. Whereas assembler programmers are free to specify
register input parameters to their routines, C compilers always generate code
that passes parameters on the stack. Part of the reason for this is flexibil-
ity: if a function is defined such as:


	struct IORequest CreateIORequest (devicename, iobuf, flags);

	CPTR devicename;

	APTR iobuf;

	UWORD flags;

	{	/* rest of function goes here */

	}


with a complex set of input and output parameters, then a stack frame can ac-
comodate all manner of argument types. However, this slows things up quite a
bit, and when calling recursive subroutines, the stack usage can become VERY
heavy!


	One good point here: modern C compilers for 680x0 systems use the
LINK/UNLK instruction pair for stack frame allocation, which is also good
practice in assembler (but a bit tedious to use every time). However, there
is a footnote to add here:most assembler programmers use LINK with a negative
offset to reserve local variable space. Compiled C code often contains LINK
instructions with positive offsets to allow referencing of the stack frames
passed to the function. Watch this - it can lead to some interesting results
if not picked up...


	Now, some hardware features inevitably condition the operation of the
compiler, and the 680x0 family is no exception. Word and long-word alignment
is important for 68000 compatibility, and a good compiler will automatically
word-align any structures of odd data size (but DON'T assume that ALL compi-
lers will do this!). Also, some compilers that have been adapted from IBM 370
compilers (yes, there are one or two!) for 680x0 use still align data on dif-
ferent boundaries depending upon their data size (so that double variables,
for example, are aligned on 8-byte boundaries!).


	A bigger problem involves sign-extension. Some other machines (most
notably PDP-11 minis among the older ones) sign-extend when converting from
char to int, for example. 680x0 machines only sign extend when explicitly
instructed to using EXT, which is always used for signed data type conversion
(e.g., from UBYTE to WORD) using casts. Also, the way in which bit fields are
assigned within a word varies from machine to machine (and on some systems,
even when moving from 68000/68010 to 68020/30/40 thanks to the hardware bit
field instructions on the later chips and their different conventions). Keep
a look out for this sort of problem!


	One area I do NOT anticipate giving too much trouble is IBM PC code
crossed over to the Amiga, unless fancy pointer tricks are used. Intel 80x86
chips store data in reverse order to Motorola 680x0 chips, namely:


  680x0 order :	Byte 1	 Byte 2	  Byte 3    Byte 4 -> increasing addr

  80x86 order :	Byte 4	 Byte 3	  Byte 2    Byte 1 -> increasing addr


for long words, and similarly for words. Z80 and 8086 programmers will know
about this sort of problem. Watch out for fancy pointer tricks on ex-IBM PC
code ported over and for problems caused by the 80x86's (bloody horrible)
segmented memory addressing when porting any code over. Tricks such as con-
verting a pointer to a UWORD to a pointer to a UBYTE and examining the con-
tents of the address pointed to, which are sadly very common in 'quick fix'
code, DON'T WORK when moved across the two machines!


C : Operators And Precedence
----------------------------

	Now for some head-banging. Operator precedence is a posh term used
to describe in what order various operations are performed when several are
mixed together in an expression. For example, assume that a C statement such
as:


		A = B + C*D/E - F*2/3 + 7 ;


exists in a C source file. Given that:


		B = 20, C = 60, D = 5, E = 3, F = 6


what is the value of A? Well, the value depends heavily upon how the compu-
tation is performed. If we simply perform the operations in left-to-right or-
der, we get:


			A = 91.8888...


which I can tell you now is WRONG.


	Without brackets to inform the programmer what order of computation
was intended, how do we get the right answer?


	This is where operator precedence comes in. The idea is that each of
the possible operators has a priority associated with it. When two operators
are encountered in quick succession, as in:


			B + C*D


then the operation whose operator has the higher priority is computed first.
Now the crunch question. Which is the higher priority operator?


	Here, where we are using only basic arithmetic, the following applies
to expressions:


	1) Division (/) has the highest priority.

	2) Then multiplication (*).

	3) Then addition and subtraction (+,-) have equal
	   priority.


But life isn't that simple in C. C has LOTS of possible operators, which in
theory can be mixed in as devil-may-care a fashion as the programmer desires.
So how do we assign precedence to all of these varied operators?


	Before displaying the complete table, another point needs to be dealt
with here. Some operators are handled from left to right. Others are handled
from right to left. Also, the exact meaning of various weird and wonderful C
operators needs to be defined (unless I tell you, do you know what |, ^ and
>> all do?) since C has its own approach to the representation of various op-
erators with different symbols. So, I'll define the meaning of each operator
first, just to eliminate confusion once and for all.


	Operator		Meaning
	--------		-------

	( )			Brackets. Forces prior computation
				of the contents, so that in a*(b+c),
				b+c is performed before the multi-
				plication.

	[ ]			Access array element.

	* ( as in *px)		Indirection. Here, px is a pointer,
				and *px is the object pointed to.

	& (as in &x)		Address of. Here, x is an object,
				and &x is the address of the object.

	- (as in -x)		Arithmetic negation. If x equals 1,
				then -x equals -1.

	! (as in !x)		Conditional negation (used in if
				clauses etc). Returns 0 (FALSE) if
				operand is nonzero, returns 1 (TRUE)
				if operand is zero.

	~ (as in ~x)		Logical negation (used in arithmetic
				expressions). Equivalent to the 68000
				NOT instruction.

	++, --			Increment/Decrement operators. See
				previous tutorials for more info.

	(type) exp		Cast (where type is the name of a
				defined data type). Forces the re-
				sult of the expression exp to be
				converted into an object of the
				given type.

	sizeof exp		Returns the size in bytes of the
				data type returned by the expression
				exp.

	sizeof(type)		Returns the size in bytes required by
				an object of the data type whose name
				is specified in brackets. On the Amiga,
				sizeof(UBYTE) = 1.

	->			Access structure or union member, as in
				rp->Layer. Here, rp MUST be a POINTER to
				a structure or union.

	.			Access structure or union member, as in
				record.name. Here, record MUST be defined
				as a structure or union variable, NOT as
				a pointer.

	,			1) Separates arguments within parentheses
				   within a function call.

				2) Separates expressions for successive
				   evaluation. See below.

	* (as in a * b)		Multiplication.

	/ (as in a / b)		Division. If the operands are both of
				an integer type (e.g. ULONG), then an
				integer division is performed (remain-
				der discarded) else a full floating-
				point division is performed.

	% (as in a % b)		Modulus. Applies to integer operands
				only. Returns remainder from integer
				division of a by b. DOES NOT APPLY
				TO FLOAT/DOUBLE OPERANDS.

	+			Addition. Obvious rules apply.

	-			Subtraction. Obvious rules apply.

	<< >>			Shift operators. a<<b is equivalent
				to LSL D1,D0 or LSL #N,D0. The ex-
				pression a>>b is equivalent to LSR
				D1,D0 if the a is an unsigned data
				type, else it is equivalent to ASR
				D1,D0.

	Relational		These are <, >, <=, >= (meanings are
	Operators		fairly obvious), and the equality
				operators == and != (equal to and
				not equal to respectively). In C, =
				means assignment, as in a = c + d.
				a<=b returns TRUE/FALSE as for the
				! operator above when used in an
				arithmetic expression.

	Bitwise Operators	These are &, | and ^. In expressions
				such as x&y, x|y, x^y, these opera-
				tions are equivalent to:

				& : AND D1,D0

				| : OR D1,D0

				^ : EOR D1,D0

	Logical Operators	These are && and ||, used in condit-
				ional expressions such as

				if (x>0 && x<max_x).

				&& is logical AND, || is logical OR.
				The expressions a && b and a || b
				return TRUE/FALSE.

	? :			See the section above on the ?
				statement for a FULL description.

	Assignment		As well as =, there are others such
	Operators		as +=, *=, etc. The operators above
				that can be thus combined are +, -,
				*, /, %, >>, <<,&, ^ and |. See below.


Now, before someone writes to me and complains about the use of the phrase
'devil-may-care' above, there are SOME restrictions upon operator mixing.
For example, those operators yielding a TRUE/FALSE result can usually take
operands of ANY data type (including float/double), but the result is ALWAYS
taken to be either int (on older C systems) or one of the Amiga integer data
types such as UWORD/ULONG. The exact choice is compiler-dependent, so EXER-
CISE CARE HERE. Always check the notes for your particular compiler and if
you want a specific data type as the result, use a cast.


	Bitwise operators should NOT be applied to float/double variables.
Moreover, it is wise to make the data type of the operands identical. Use of
constructions such as:


	UWORD a;

	ULONG b, c;

	c = (b | a);


is NOT advised -  use instead


	c = (b | (ULONG) a);


if this is what you REALLY want.


	One more point. Assignment operators. C allows multiple assignment
types, so that:


	yyval[yypv] = yyval[yypv] + yyval[yynn];


which nobody would want to type the hard way if avoidable, can be retyped as:


	yyval[yypv] += yyval[yynn];


The following is a list of equivalent expressions defining the assignment op-
erators:


		a = a + b;		a += b;

		a = a - b;		a -= b;

		a = a * b;		a *= b;

		a = a / b;		a /= b;

		a = a % b;		a %= b;

		a = a >> b;		a >>= b;

		a = a << b;		a <<= b;

		a = a & b;		a &= b;

		a = a ^ b;		a ^= b;

		a = a | b;		a |= b;


Here, a and b MUST be arithmetic type operands, except for += and -=, where
a can be a pointer variable (however, b MUST STILL be an arithmetic variable,
NOT another pointer variable).


	And just to confuse the novice, not only can the , character be used
to separate function arguments as in:


	status = CompareString(str1, str2);


but as an operator in its own right! In this example:


		x = (t=3, t+2);


the expression t=3 is computed first, then t+2 is evaluated. The variable x
is then given the value of the rightmost expression in the sequence (some of
the most sophisticated compilers allow multiple commas as in:


		x = (t=3, t+2, t*7);


in which the leftmost expression result is discarded once the next expression
in the sequence is computed).


	Right. Having got that little lot out of the way (I warned you that
this was a head-banging section) now let's consider precedence (i.e., which
operators are used first). The complete table is (in order of descending pre-
cedence) given below, where the highest priority operators are at the top of
the table:


	Operator				Grouping
	--------				--------

	() [] -> .				  L-R

	! ~ ++ -- - (type) * & sizeof		  R-L

	* / %					  L-R

	+ -					  L-R

	<< >>					  L-R

	< > <= >=				  L-R

	== !=					  L-R

	& (bitwise)				  L-R

	^					  L-R

	|					  L-R

	&&					  L-R

	||					  L-R

	? :					  R-L

	=, +=, -= etc				  R-L

	,					  L-R


Here, L-R means left-to-right grouping (which means that the operand on the
left is evaluated first), and R-L means right-to-left grouping. Operators of
equal priority occur on the same line within the table.


C : Advanced Odds And Ends
--------------------------

	One advanced feature of modern C compilers that MUST be covered here
is the ability to embed 68000 assembler code within a C program. This is ac-
complished normally by using preprocessor directives #asm and #endasm, within
which the assembler code is written, as in:


	#asm


	loop		cmp.b	(a0)		;EOS met?
			beq.s	exit		;yes-skip
			cmp.b	(a0)+,(a1)+	;chars match?
			beq.s	loop		;back if so
	exit		rts			;return status


	#endasm


However, the usefulness of this feature as an alternative to DevPac, for ex-
ample, is limited by the (obvious) lack of assembler include files within the
C package (and do you REALLY want to convert all of the '.h' files into their
assembler equivalents? I thought not!), compatibility problems (not all ass-
emblers support DevPac's features such as RS directives, which I for one use
very heavily within my own code, and which are heavily used within the stan-
dard include files) and the problems of having to edit the text in a separate
text editor, exit to the compiler, and then possibly pass the object code to
a separate linker such as BLink (a most tedious procedure).


	The use of this feature becomes further limited without knowledge of
the parameter passing mechanism used by the compiler. Each compiler has its
own conventions for parameter passing, and while some provide a bare minimum
interface, others provide (with documentation) a whole range of interfacing
options including reserved variables for storing CPU register contents and
parameters from functions accessible via assembler labels, or even direct ac-
cess to all of the compiler's labels for your program variables within the
assembler should you need it.


	In tandem with the above, another advanced feature is the ability of
C to provide pointers to FUNCTIONS. These can be passed to other functions in
a manner analogous (but not identical) to pointer variables, and all manner
of wonderful manipulations performed upon them. For example, it is possible
to write a routine to integrate a function (geometrically, this is the same
as finding the area under the curve on its graph) using a procedure called
Simpson's Rule (no, NOT Bart Simpson, dummy!). This requires a bit of head-
scratching to come to terms with, but here goes anyway.


	Simpson's Rule is actually quite neat. Given a function f(x), it is
possible to work out the area under the curve between two values of x, say
x=a and x=b, to a good degree of accuracy using this rule. I won't describe
how Simpson's Rule came into being:that belongs in a mathematics textbook.
But I will describe how to use it.


	What you do is this. Given your endpoints, x=a and x=b, split it up
into lots of small intervals. If you want to split it into N intervals, then
you compute:


			E = (b-a)/N


which gives you the interval size. One word of warning:N MUST BE EVEN.


	Having obtained E, you then use it in a loop. Calculate all of the
x values from x=a to x=b in sequence. These are fed into our function and
the results added together.


	So, if our interval from x=a to x=b looks like this:


		x , x  ,x  ,x  ,x  ... ,x
		 0   1   2   3   4	 N


then:


		x	=	x + (E * i)
		 i		 0


If furthermore, for our function f(x), we define:


		f	=	f(x )
		 i		   i


then Simpson's Rule consists of calculating:


		E
		- (f + 4f  + 2f  + 4f + ... + 2f   + 4f   +f )
		3   0    1     2     3	        N-2    N-1  N


Right. So we can write a function to do this, as it's a straightforward add
and multiply job. But what if we want to change the function each time, say
in a manner similar to a spreadsheet?


	Well, the normal way is to pre-compute all of the f's in an array, &
then compute the above using the array elements instead of the f's. But if we
are selecting our function interactively? What if we want to type in our new
function each time during the program's execution, instead of the tedious and
messy business of editing and re-compiling the source?


	Let's make the following assumptions:


	 One, your C compiler has in its library files an eval() function,
which takes a string pointer as its argument, and returns a double value:


	double eval(string);

	UBYTE *string;

	{

		/* function definition goes here */

	}


so that, given a string of the form:


		2*pi*sin(omega*x)+(L/omega)*cos(omega*x)


eval() will spit out the required value given the values of the constants pi,
omega and L, and the variable x. If your compiler doesn't have it, have fun
writing it...


	Two, we want to write our function to evaluate Simpson's Rule in such
a way that we can call it using something like:


	simpson(function, icount);


where the 'function' argument determines which function is computed. So, let
us look at the code:


	double simpson (function, icount, lower, upper);

	double (*function) ();

	double lower, upper;

	int icount;

	{

		double tmp, interval, xvalue, fvalue, coeff;

		int i;

		tmp = 0.0;

		xvalue = lower;

		coeff = 1.0;

		interval = (upper-lower)/( (double) icount);

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

		{

			fvalue = (*function) (xvalue);

			tmp = tmp + coeff * fvalue;

			xvalue += interval;

			if (coeff != 4.0)

				coeff = 4.0;

			else

				coeff = 2.0;

		}

		tmp -= fvalue;

		tmp = tmp * interval / 3.0 ;

		return (tmp);

	}


Gee, wasn't that horrible? Now to tie up the loose ends.


	In main(), we need function declarations for the functions that are
to be passed to simpson(), either as simple declarations of existence in the
main() function, such as:


	main()

	{
		double sin(), cos(), tan() ;

	/* rest of main() */

	}


or as actual definitions of the function's code (there exist examples of such
definitions in C standard math libraries, which are NOT to be confused with
the Amiga math libraries...). Note that in the declaration above, since it is
known that sin(), cos() etc., are functions, not variables, the * operator is
not needed, and the & operator is not needed when calling simpson()< as in:


		simpson(sin, 100, 0.0, PI/2);


(here assume that PI exists - some Amiga math libraries have a #define for pi
included within them).


	Those familiar with calculus will know that many functions can be in-
tegrated analytically (i.e., by manipulating the functions algebraically), &
hence that using Simpson's Rule on functions such as sin(x) is a mite waste-
ful. However, there exist many functions that CANNOT be thus integrated, and
for these functions Simpson's Rule is one of a small number of avenues open
to mathematicians. The above code will handle Simpson's Rule for any function
passed to it, and if your C compiler possesses the eval() function, then it
is possible to integrate ANY function by calling:


		simpson(eval(s),icount,lower,upper);


assuming that eval() exists as described earlier and that s is a pointer to a
string (char * on older machines, UBYTE * on the Amiga).


	Note the usages. When calling one function within a second function,
where the first function is referenced via a pointer, the calling synopsis is
of the form:


		(*f) (args)


This is written as such because here, f is a pointer to the function, hence
*f is the function itself. The brackets are needed - compare:


		double (*function) ();


which says that 'function' is a pointer to a function that returns a double,
whereas:


		double *function ();


says that 'function' is a function returning a pointer to a double - you can
appreciate the difference! Oh, and if you want a pointer to a function that
returns a pointer to a double, you will need:


		double *(*function) ();


Now breathe out...


C : Anachronisms
----------------

	C is an evolving language. The K&R standard is simply the earliest to
be adopted as a universal standard, and so many compilers come with a certi-
fication to K&R standard. After the K&R standard came the ANSI Standard for
C, courtesy of the American National Standards Institute.


	Before K&R, certain features of the language differed in the symbol-
ism used. These anachronisms are supported by some compilers, but it is to be
expected that modern compilers and future expanded compilers (such as C++ and
Object-Oriented C to name two) will have nothing to do with these construct-
ions, reporting them as errors. This will render some older C code non-port-
able, but provided that your code does not contain these weird constructs, it
is safe to assume that K&R standard C will compile on an ANSI compiler, or if
your budget runs to it, a C++ compiler!


	The two constructs I shall illustrate come straight from K&R. First
is the alternative syntax for assignment operators. Older C code still exists
that contains, for example:


		x =- 1;


instead of:


		x -= 1;


This is another way of decrementing x apart from x-- and --x, but if written
without the spaces:


		x=-1;


becomes ambiguous. It could mean either of:


		x =- 1;		or	x = -1;


Because of this ambiguity, the format:


		x-=1;


was adopted to remove such problematic constructs. Worse still was:


		x=*y;	instead of x*=y;


where the ambiguity could be either:


		x = *y;		or		x =* y;


Hence the adoption of x*=y instead. All assignment operators underwent this
change to remove sources of ambiguity including the potentially disastrous
one above (where the compiler could interpret x=*y as a reference involving
a pointer, which would have all manner of wonderful results if y was NOT a
valid pointer!).


	One last anachronism is in the field of initialisation of variables
in the declaration section. It is legal to have declarations such as:


		UWORD	x = 1;

		ULONG	a = b = c = 0;


Previously, initialisation looked like this:


		UWORD	x	1;


the multiple initialisation not being allowed. The change was made, for one
simple reason. The initialisation:


		int	f	(1+2);


resembles a function definition closely enough to confuse compilers! Watch
out for this sort of stuff in old C code ported over to the Amiga (especi-
ally old PDP-11 code!).


This wraps up the fourth C tutorial for ACC members. Sorry about missing out
this stuff in the earlier files, but those files contained the most important
material for converting the Amiga ROM Kernel Manual C code to assembler, so I
thought I'd hand those over first. This file should clear up all of the rem-
aining queries. Menawhile, as you may have guessed...


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



			Dave.




