This is a condensed version of the Hisoft C reference manual for the Sinclair Spectrum. The main sections not covered are:
The compiler is based entirely in RAM, together with an editor, the source text which is being developed, and the resulting machine code. There is no intermediate pass through assembly language or linkers such as is normally found in C compilation systems.
After loading the program and typing 'n' in response to the "copy to Microdrive?" question, C code can be entered directly; type SymbolShift-I (end of file) to finish and the compiler will ask you "type y to run program" (any other key will return to the sign-on message). This is the simplest way of entering a program, without using the editor.
The keyboard is used in lower-case mode all the time. Pressing a key usually returns the ASCII code of the lower-case letter or digit on that key. However, pressing Enter returns 10 rather than 13, as C uses Newline/Linefeed at the end of a line, not Carriage-Return.
Pressing SymbolShift plus another key returns the ASCII code of the red symbol engraved on or by the key, rather than the BASIC keyword. This includes <=, <> and >= on the Q, W and E keys, which have been assigned values of 29, 31 and 30 respectively. SymbolShift-I returns the EOF value -1 and is echoed as CHR$(137) on the display; this is used to terminate files typed in from the keyboard.
CapsShift is also altered slightly. CapsShift-6 now returns 16 (not 10, which has been used by Enter); CapsShift-0 returns 8 (not 12) and CapsShift-5 returns 12 (not 8).
The three standard C files "stdin", "stdout" and "stderr" are assigned to the Spectrum keyboard stream, upper display screen and upper display screen (again) respectively. Some translations are done on the characters.
Input and output to other devices is done to files using "file- pointers". The file-pointers used for I/O in C are used to represent stream numbers on the Spectrum. They have type pointer-to-FILE so that they are compatible with file-pointers on other systems, but actually their value (as a bit pattern) is just the Spectrum stream number. Keyboard I/O uses getchar() and putchar() respectively; these correspond to getc(0) and putc(0).
Before using getc or putc you must have a file-pointer. For the keyboard and display 0 can be used as mentioned above. You can also use stream 1 for the keyboard / lower screen, 2 for the upper screen, and 3 for the printer.
For cassette and Microdrive you must call "fopen" to get the file- pointer. The file-pointer for cassette is 16; for Microdrive it will be the highest unopened stream number (normally 15). In addition, you can open streams in some other way - from BASIC or by a machine code routine - and then just pass the stream number to getc or putc.
All I/O on cassette or Microdrive is done at a character level using getc(fp) and putc(c,fp). Before any tape I/O it is necessary to call fopen(filename,mode). On output this constructs a header block and writes it to tape, whilst on input it searches the tape for a matching header block. putc and getc can then be used for character I/O and finally fclose tidies up and writes the last block of an output file.
When the system is about to write to cassette it changes the border colour to RED for about five seconds before doing so; the cassette should be started in RECORD when this happens. On input a RED border is displayed for about a second to indicate that the cassette should be started in PLAY. When the program has read a block the cassette should be stopped so that the program can process the contents of the block, and then restarted when it requests the next block. The compiler is capable of compiling source from a block even if the cassette is left running.
The compiler uses the Spectrum temporary colour attributes to control the Ink and Paper colours on-screen, which are set up from the permanent colour attributes when the compiler displays the sign-on message. To use different colours simply set INK and PAPER prior to loading the compiler (or use the editor B command to return to BASIC).
1.5 Making a Backup
1.6 Prices, Royalties, Publishing & Copying
1.7 Example Programs
To enter the editor from the compiler, type CapsShift-1 (or EDIT) then Enter. You will see a vertical block when you press EDIT, and the message "edit:" when you press Enter, followed by the prompt '>'.
To enter the editor after a compiler error, select the EDIT key while the compiler is waiting for a keypress after the error message, and the editor will start on the line causing the error. To return to the C compiler type C-Enter, or to return to BASIC type B-Enter.
In response to the '>' prompt the command format is:
C N1, N2, S1, S2
C the command to be executed
N1 a number in the range 1-32767
N2 a number in the range 1-32767
S1 a maximum 20 character string
S2 a maximum 20 character string
Most editor commands do not expect all five parts of a command line. The editor remembers previous numbers and strings and will use these former values if you do not enter them when required. N1 and N2 are initially 10 and S1 and S2 are initially empty. The comma is the default argument separator and spaces are ignored, except in strings.
Text may be entered into the text file either by typing a line number, a space and then the required text, or by using the 'I' command. If you type a line number without any text, followed by Enter, then that line will be deleted from the text file.
Insert Text I m,n
Insert text from line m to line n (replacing any text already there).
Display Text L m,n
List lines m through n on-screen. This command does not use previously entered defaults. Defaults are always 1 for m and 32767 for n, thus 'L' on its own will always list the entire file. After each screen is displayed press EDIT to return to the editor or any other key to continue with the listing.
List Length K n
Set the display length before a pause to n lines.
Print Text W m,n
Write lines m through n to the printer; defaults are as for 'L' above. Press BREAK to abandon printing and return to the editor.
View Defaults V
Displays the current argument delimiter and default values for N1, N2, S1 and S2. This command also displays the start and end addresses of the text file in decimal.
Set Delimiter S,,d
Change the argument delimiter to character d.
To Compiler C
To finish editing and return to the C compiler, press C-Enter.
To BASIC B
To finish editing and return to the BASIC, press B-Enter. To return to the C compiler type: RANDOMIZE USR 25200
Delete Lines D m,n
Delete lines m through n from the text file.
Move Line M m,n
Move line m to line n, replacing whatever was there before. Line m is left unchanged (thus this is really Copy, not Move).
Renumber Text N m,n
Renumber the entire text file starting with a first line number of m and in steps of n.
Edit a Line E n
Edit line number n. The special editing commands are:
|CapsShift-8 - move to the next character|
|CapsShift-5 - move to the previous character|
|finish editing and save the changes|
|finish editing and ignore the changes|
|restore the line as it was before any changes|
|list the rest of the line being edited|
|delete the character at the current cursor position|
|delete all characters from the current cursor position to the end of the line|
|insert characters at the current cursor position - pressing Enter will return to the main Edit mode|
|insert at the end of the line|
|overtype characters until Enter is pressed to return to main Edit mode|
|find the next occurrence of the 'find' string previously defined using the 'F' command below. This will end Edit on the current line if it does not find another occurrence of the 'find' string. If an occurrence is found within the previously specified 'F' line range then Edit mode will be entered for the next matching line, with the cursor positioned at the start of the matched string.|
|substitute the previously defined 'F' command 'substitute' string for the 'find' string where the cursor is and then search for the next occurrence of the 'find' string. Together with the 'F' sub-command above, this allows stepping through the file and optionally replacing occurrences of the 'find' with 'substitute' string.|
Find String F m,n,f,s
Search text in the line range m through n for string f; enter Edit mode on the first matching line.
These commands use the same simple scheme as the compiler to
distinguish between cassette and Microdrive files. To use a Microdrive
file called, say, FRED, on Microdrive 2, put the drive number and a
colon before the file name in commands, thus:
Write Text P m,n,s
Put text lines m through n onto cassette or Microdrive with file name s. The line numbers are not recorded in the file. Make sure that your tape recorder is set to RECORD before entering this command.
Read Text G,,s
The cassette or Microdrive is searched for file name s, which if found will be loaded at the end of the current text. Line numbers are attached to the lines as they are read in, increasing in steps of 10.
2.18 An Example Session Using the Editor
3.1 Differences from Kernighan & Ritchie
#define identifier macro
This command allows for limited 'macro expansion' - the definition of constants. #define macro arguments are not supported, nor the #undef command.
This frees space in memory by removing compiler error messages.
Followed by a '+' or '-' sign, switches the compiler listing mode on or off as appropriate.
Followed by a '+' or '-' sign, turns direct execution mode on or off. Direct execution mode allows programs and functions to be tested as they are written. Once this mode is enabled, functions can be invoked, variables may be assigned, loops can be run and any other statement can be executed. Typing a function name followed by its required parameters and a semi-colon will execute the function.
This command is probably most usefully seen as a command to initiate a compilation, although its more precise effect is to include a specified file in the compilation of another. The three main forms are:
Compiles the current source text in memory.
#include filename -or- #include "filename" -or- #include <filename>
Causes the named file to be included in the current compilation.
This allows for library file inclusion, and only compiles those functions in the named file which have been used somewhere in the main file.
This tells the compiler to save the product of the compilation to cassette or Microdrive under the file name given. The resultant file can be loaded and run with
LOAD "filename" CODE and
RANDOMIZE USR 25200
These notes are provided to explain and motivate particular aspects of the C programming language or of the Hisoft C compiler.
Variables in C programs are each of a particular type, which may be one of the predefined types, or a user-defined type declared with a typedef declaration, or an anonymous type (eg int *(*f())). The C compiler checks that the variables used in an expression are of compatible types in order to help detect programming errors. In C this checking is by a method known as STRUCTURAL EQUIVALENCE. This means that two variables have the same type if their declarations have the same structure no matter where they are declared. This notion is also employed when operators such as * (indirection) and & (address) are applied. So for example if:
then polar, z, and *ptr_Cartesian are all of equivalent types. This is different to some other languages where they need to have the same named type to be equivalent or to be declared at the same place. Structural type equivalence is more flexible, but can lead to obscure bugs if misused and it is generally good practice to give a name to complicated types by using typedef.
This section gives some details of the code produced by the compiler, with particular reference to the store layout and use of the machine. It is intended to help you interface other programs to C programs and in particular to help you make use of the "inline" statement.Source Format
C source is basically just a string of ASCII characters, divided into lines by NEWLINE characters (10, often known as LINE-FEED). There are no CARRIAGE-RETURN characters (13), although these will be accepted in a source file by the compiler, so other editors and even other computers can be used as tools with which to produce Hisoft C programs.File Format
Files are provided at the character level (ie. fopen, getc, putc, fclose).
On cassette, files are stored as a sequence of data blocks which follows a header block. The header block is a normal Spectrum header. The data blocks are each 514 bytes long, comprised of a character count in the first two bytes and up to 512 characters in the remainder of the block. The top bit of the character count is set to indicate the last block in the file.
Normal Spectrum CODE files are used on Microdrive, except that a start address and length of 0 are used. This means that most of the extended cataloguing programs available for the ZX Spectrum microdrives will return a file length of zero.
The compiler, and compiled programs, can open any type of Microdrive file for reading. This is done automatically.Function Linkage and the Stack
The Z80 processor stack (SP) is used for function linkage and local variables.
The caller evaluates each argument in left-to-right order and pushes them on the stack in turn before calling a function (so the last argument is on the top of the stack). THERE MUST BE EXACTLY AS MANY ARGUMENTS AS THE FUNCTION EXPECTS. The caller then enters the function with a CALL instruction to the start of the function.
The called function then takes over and it first pushes the IX register on the stack and loads IX with the current value of the stack pointer (SP). Space is then allocated for any automatic local variables by decrementing SP. The function now executes, using IX to access the arguments and its locals. Finally it recovers the previous value of IX (for the caller), salvages the return link, and discards the local variables, linkage, AND ARGUMENTS from the stack.
The result of the function is returned in HL, and also in BC.
There is a variation on this mechanism which is used for variadic functions (ie. those that take a variable number of arguments). In this case, after pushing all the arguments on the stack, the caller pushes the total number of bytes of arguments including two bytes for the total itself. This total appears as the last argument to the called function. The called function can use the total to access its other arguments and MUST use it to discard the arguments from the stack before returning.Register Usage
Neither the compiler nor the compiled code use the alternate register set, the IY register, or the I or R registers. These registers ARE used by the Spectrum I/O system and in particular the IY register must always point at ERR_NR.
The compiler and the compiled code run with interrupts enabled.
The stack pointer (SP) is used normally and stack discipline should be observed. The IX register is used as a frame pointer as described above. The HL register is used to return the value of expressions and particularly function results. The BC, DE, A and F registers are used as general working registers.Data Storage
There are three kinds of data storage: constant, static, and automatic.
Storage for constants is allocated inline with the generated code; this includes numbers, characters, and strings.
Static storage is used for global (external) variables and for all static variables. This storage is allocated starting at the top of memory at RAMTOP and working downwards. The stack is below the static storage, and is moved down when necessary to keep it so. Static storage is accessed directly by addresses in the compiled instructions.
Automatic storage is used for automatic local variables, arguments and function linkage, and temporary working store. It is allocated on the stack and accessed using SP and IX.
Storage for individual variables is allocated in the same way, regardless of whether automatic or static storage is used. First is storage for the basic types, then for derived types:
|int||2 bytes, least significant byte at lower address|
|unsigned||2 bytes, least significant byte at lower address|
|pointer||2 bytes, least significant byte at lower address. Contains address of pointed-to object|
|array||n * s bytes, where n is the array bound and s is the size of each element. The first element (a) is at the lowest address. A multi-dimensional array such as a[m][n] is treated as an array with bound m of arrays with bound n of the elements. So in the case of a character array element a[i][j] is at address a + i*n + j|
|structure||s bytes, where s is the sum of the sizes of all the members of the structure. The first member of the structure to be declared is at the lowest address (as described in Kernighan & Ritchie)|
|union||s bytes, where s is the maximum of the sizes of all the members of the union. All members will be aligned at the lowest address (ie. any spare space for a particular member will be at the high end of the union)|
The compiler and stand-alone programs translated using the compiler all load at 25200 and are entered there also. This address leaves space below the compiler for two Microdrive channels, or one Microdrive channel and a cassette pseudo-channel. There is also room for a VERY small BASIC program.
The compiler will use store up to RAMTOP, and it moves RAMTOP down beneath itself for protection. This means that it is necessary to CLEAR to a larger value if you want to use the Spectrum for BASIC again after leaving the compiler.
Translated programs use the memory between 25200 and the RAMTOP value which was set before using the compiler to translate the program. However, these programs do not move RAMTOP themselves.
The library comes in three parts: the Built-ins, the header, and the
The Built-ins are functions which are in the run-time package for efficiency and are therefore always contained in a program and can simply be called.
The header is a C source called "stdio.h" provided on the compiler tape. It contains constant and type definitions for the library and also contains the "min" and "max" functions. It should be included (#include "stdio.h") at the start of all programs which use the library.
The library proper is also supplied as C source in the file "stdio.lib" on the compiler tape. It contains the source of most of the library functions. This file should be selectively included at the end of each program which uses the library by means of a library- search control line (#include ?stdio.lib?).
int max(n, ...) auto
Returns the value of the greatest of its integer arguments.
int min(n, ...) auto
Returns the value of the smallest of its integer arguments.
Returns the absolute value of its argument.
Returns -1 if the argument is less than zero, 0 if the argument is zero, and +1 if the argument is greater than zero.
typedef char * __char_ptr;
Returns the value of the memory byte at location "address".
void poke(address, value)
Puts the low eight bytes of value into memory at location "address".
Scans the string "s" and returns the binary value of the ASCII number in it. The conversion stops when it finds the first non-digit character. The value 0 is returned if no number is found.
void qsort(list, num_items, size, cmp_func)
int num_items, size;
Sorts a list of "num_items" items each of size "size" starting at "list" into ascending order. "cmp_func" is a pointer to a function which will compare two items in the list; eg. the standard function "strcmp" can be used if the items are strings. The function should take two pointer arguments, so a call looks like:
and the function should return an integer:
-1 if *x < *y
0 if *x == *y
+1 if *x > *y
A common structure for the list is a two-dimensional array, num_items long and size bytes wide:
char list [num_items] [size];
char *strcat(base, add)
char *base, *add;
Inserts a copy of the string "add" at the end of string "base". The function returns a pointer to the start of the "base" string as its result.
int strcmp(s, t)
char *s, *t;
Compares two strings, byte for byte, and returns 0 if the two are identical, >0 if s>t or <0 if s<t.
char *strcpy(dest, source)
char *dest, *source;
Makes a copy of the "source" string in the "dest" string.
Returns the length of a string.
Returns TRUE (+1) if the character is an alphanumeric and FALSE (0) if not.
Returns TRUE if the character is a letter and FALSE if not. (Built-in)
Returns TRUE if the character is ASCII (less than 0x80).
Returns TRUE if the character is a control character.
Returns TRUE if the character is digit. (Built-in)
Returns TRUE if the character is a lower-case letter. (Built-in)
Returns TRUE if the character is a printing one.
Returns TRUE if the character is punctuation (ie. printable but not a letter or a digit).
Returns TRUE if the character is white space (space, newline or tab). (Built-in)
Returns TRUE if the character is an upper-case letter. (Built-in)
Returns the lower-case equivalent of an upper-case character, otherwise returns the character unchanged. (Built-in)
Returns the upper-case equivalent of a lower-case character, otherwise returns the character unchanged. (Built-in)
char *calloc(n, size)
unsigned n, size;
Allocate space for "n" items of "size" bytes each. It returns a pointer to the start of the space or NULL if there is no space.
Return a block of memory to the free-store chain for re-allocation later by "calloc". You must return [sic] a copy of the pointer supplied by "alloc" [sic] when the storage was obtained.
Allocate "n" bytes of physical memory for use by calloc(); it is not normally called from anywhere else.
void swap(p, q, length)
char *p, *q;
Swaps the contents of the two areas of memory pointed to by "p" and "q", each "length" bytes long. (Built-in)
void move(dest, source, length)
char *dest, *source;
Moves "length" bytes of memory starting at "source" into the area starting at "dest". The copy is done in the non-destructive direction if the areas overlap. (Built-in)
FILE * fopen(name, mode)
char *name, *mode;
Opens a file "name" in mode "mode" for character-level I/O. "mode" may be "r" for reading and "w" for writing. It is not possible to append to an existing file, and if an existing Microdrive file is opened for writing it will first be erased. "fopen" returns a file pointer, or NULL (ie. 0) if there is an error. (Built-in)
Close the file indicated by the file-pointer "fp". If the file is being written to this ensures that the last block of data is written. (Built-in)
Reads the next character using the file-pointer "fp". It returns EOF (-1) if the end of the file has been reached. (Built-in)
int ungetc(c, fp)
Puts the character "c" back onto the file "fp", so that it is the next character to be read with getc() or getchar(). Note that scanf uses ungetc, so ungetc cannot be used after scanf without an intervening call to getc. (Built-in)
int putc(c, fp)
Sends the character "c" using the file-pointer "fp". It also returns the character as its result. (Built-in)
Get a character from stdin. Incoming characters are collected into a line buffer until ENTER is pressed. The DELETE key can be used to edit characters as they are typed. A flashing cursor is displayed and characters are echoed on the display as they are typed. (Built-in)
Put a character to stdout. The character is displayed on the screen. It also returns the character as its result. (Built-in)
This function closes all files which the program has open and then exits from the program by calling "_exit" (see below). The parameter "n" is passed out as the result of the program; a return value of 0 means success, other values indicate an error by causing the corresponding Spectrum error report to be displayed.
char *fgets(s, n, fp)
Read string "s" from file-pointer "fp", stopping when a NEWLINE is read or "n"-1 characters have been read. The string will be terminated by a NULL character which is added after the NEWLINE character. The return values is normally "s", but if the end of file has already been reached when "fgets" is called it will return NULL.
void fputs(s, fp)
Outputs the string "s" to the file-pointer "fp".
Reads string "s" from stdin. It is similar to fgets except that it has no maximum character count, and also the NEWLINE is OVERWRITTEN by the terminating NULL.
Outputs the string "s" to stdout.
void printf(control, arg1, arg2, ...)
"printf" converts, formats, and prints its arguments on "stdout" under control of the string "control". It behaves as described in Kernighan & Ritchie, except that floating point specifications are not supported. (Built-in)
void fprintf(fp, control, arg1, arg2, ...)
Behaves like "printf" except that output is performed using the file- pointer "fp" instead of "stdout". (Built-in)
void sprintf(s, control, arg1, arg2, ...)
char *s, *control;
Behaves like "printf" except that output is placed in the string "s" instead of being sent to "stdout". (Built-in)
int scanf(control, arg1, arg2, ...)
ALL OTHER args MUST BE POINTERS;
This function is the input analogue of printf, providing many of the same conversion facilities in the opposite direction. It reads characters from stdin, interprets them according to the format specified in "control", and stores the results in the remaining arguments WHICH MUST ALL BE POINTERS!! The control string usually contains conversion specifications which are used to direct inter- pretation of input sequences. It may contain:
int fscanf(fp, control, arg1, arg2, ...)
This function behave like "scanf" except that its input is obtained from the file attached to "fp" rather than stdin.
int sscanf(s, control, arg1, arg2, ...)
char *s, *control;
This function behave like "scanf" except that its input is obtained from the string "s" rather than stdin.
Inputs a character directly from the keyboard, with no conversion of character codes. There is no cursor and nothing is echoed to the display. (Built-in)
Tells whether a key has been pressed on the keyboard, returning TRUE if so and FALSE if not. The function does not read the key, and if it returns TRUE then the keyboard must be read to get the key and reset the keyboard before keyhit() is used again; otherwise, it will continue to return TRUE every time. (Built-in)
This function immediately exits from the program and returns to the system. The argument is printed as the corresponding BASIC report.
void long multiply(c, a, b)
char *a, *b, *c;
Multiply two 32-bit numbers: c = a * b;
The numbers are represented by an array of four characters (or a pointer to such an array), with the least significant eight bits in array and so on to the most significant bits in array. The numbers are unsigned.
void long add(c, a, b)
char *a, *b, *c;
Add two 32-bit numbers: c = a + b;
void long init(a, n1, n0)
unsigned n1, n0;
Initialise a 32-bit number. n1 provides the most significant 16 bits and n0 the least significant 16 bits.
void long set(a, n, d)
unsigned n, d;
Initialise a 32-bit number. n provides 16 bits to initialise and d tells where to place them in the number (0 to 3).
void long copy(c, a)
char *a, *c;
Copy one 32-bit number to another place: c = a;
Returns a 16-bit pseudo-random number.
"Seeds" the random number generator.
void plot(on, x, y)
int on, x, y;
Sets the pixel at screen location (x,y) to either the current ink colour (n is TRUE) or paper colour (n is FALSE).
void line(on, dx, dy)
int on, dx, dy;
Draws a line from the current plot position to a point dx pixels to the right (or left if dx is negative) and dy pixels up (or down if dy is negative). The "on" parameter behaves as it does with "plot".
Changes the current temporary INK colour using the Spectrum colour codes. The new colour is returned as the value of the function, or -1 if the colour code was invalid.
Changes the current temporary PAPER colour in the same way as "ink" works.
Clears the upper part of the screen.
void beep(duration, pitch)
int duration, pitch;
Makes sounds using the Spectrum's loudspeaker. The duration is given in tenths of a second whilst pitch is given in Hz (Hertz). A pitch of 0 causes a period of silence - a rest.
5.2-5.66 ERROR 0 - ERROR 64
5.67 Common Mistakes in C Programs