Alexios' Home Page

C Programming on the Oric, Part 1 of 8 

HomeWritten ramblings ► C Programming on the Oric, 1/8 ►

1 Introduction

This series of articles is a C language tutorial, focused on using C efficiently on Oric computers. The language combines a set of features which make it an excellent choice for most programming tasks, from system programming to Artificial Intelligence. Unfortunately, it was not designed with small eight-bit systems in mind. As a result, the Oric implementation of C is a cut-down version. Although this is supposed to be programming on the Oric, I will try to provide a generic C tutorial, stressing differences between ANSI C and the compiler we use on the Oric. I might fail utterly, in which case Kernighan and Ritchie will appear to me in a vision and damn me to eternal debugging, but it will be fun anyway.

So, the most important question: why use C? Why not stay with Assembly, Forth or Basic? There are many answers to the Question. C is a very compact language: it comprises a minimalistic core of less than fifteen statements. Everything else (including I/O, maths, and many data types) is in external libraries, which can be linked to the program at will. This allows for very small programs and extreme flexibility. Also, C is a high-level language, but, depending on the style and knowledge of the programmer, it may work with all the power of Assembly, or with all the structure and readability of Pascal. This makes it a good choice for writing fast Oric programs easily and quickly. And of course, a language which provides such ample opportunities for puns and jokes has to be good, right?

These articles will assume you are using an 48k Oric-1 or Atmos as your testing platform. Since there is no native Oric C compiler, we use a cross-compiler (i.e. the compiler runs on one platform, and produces code which will run on another). The compiler is David R. Hanson's retargettable ANSI C compiler, lcc. The Oric version, lcc65, runs on IBM PCs and compatibles (DOS or Linux based). You can run your programs either on a real Oric, or on Fabrice Frances' Euphoric emulator.

2 Using the Compiler

Type in the following archetypal program using your favourite PC text editor.

#include "stdlib.h"

/* The famous Hello World program! */

void main()
{
	printf("Hello world!\n");
}
case-sensitive language. Save it as hello.c. Exit the editor, and enter the following:
cc65 hello
After a while, the DOS or Linux prompt will be displayed. Your program is now compiled. Yes, that was all. If you get any error messages, you probably made a typo. Check the file and try again.

Now, let us run the program. A successful compilation will yield a .out file (hello.out in this case). This is a ‘tape’ file. Run Euphoric and load it:

Ready
CLOAD"HELLO.OUT"
Hello world!

Ready
So, there you have it: your first C program on the Oric. Now, let us see why it does what it does.

To understand that, we need an important piece of information: the C compiler is not a single program, but a pipeline of different programs:

In the case of the Oric compiler, the linker (by Vaggelis Blathras) runs between the compiler and assembler, and there is also an extra program which translates the final machine code object file into an Oric ‘tape’ file.

All this might sound a bit too technical (and definitely useless). However, each of these stages has its own command set. In the Hello World program, for example, the first line is handled by the preprocessor.

The #include "stdlib.h" directive instructs the preprocessor to literally include the file stdlib.h at the point in hello.c where the #include command was seen. The preprocessor actually processes and outputs the contents of the specified file, and then goes on with the rest of the original file (of course, #include directives can be nested).

The next line is a comment. Anything between /* and */ is considered a comment. Comments may be located anywhere in a line. They may span lines.

Next, we define a function. A C program is split into functions, each of which calls other functions. Even the main program is a function, called 'main'. This function is automatically called when the program starts. void means the function returns nothing (this makes C functions differ from the mathematical concept of a function). main is the name of the function. '()' means that the function accepts no parameters. Note that you have to include the parentheses even if the function accepts no parameters. The parentheses are C's way of knowing that we are calling or declaring a function (something like the $ in BASIC strings).

The curly brackets ('{' and '}') denote the beginning and end of the body of the function. printf() is a function (as you might guess by the '()'). It prints the string passed to it as a parameter (note that C strings are enclosed in double quotes: "Hello world!\n"). printf() is obviously not defined in our little program. It is not defined by C itself, either (remember, C has no built- in functions). It's declared inside stdlib.h (footnote: in ANSI C, printf() is declared in stdio.h, the standard input/output functions library. Things are different on the Oric, but they might change as the Oric C compiler progresses.). This is why we need to #include "stdlib.h". By the way, the \n at the end of the string means ‘start a new line’. C actually translates it to the ASCII code for CR (Carriage Return, CTRL-M or RETURN).

Another interesting point is the final semicolon (;). In C, all declarations and statements end with this symbol. Try not to forget it; the compiler will stop with all sorts of weird error messages. Since semicolons are used to delimit declarations and statements, white space (spaces, TABs and RETURNs) is not important to the compiler. As long as you use semicolons, you might as well write your whole program in a single line (I wouldn't recommend it: it makes debugging a nightmare).

3 Simple Data Types and Variables

Like most high-level languages, C has its own set of data types. Only simple data types are defined by C: all other data types can be derived from simple ones. They are defined in (guess what) libraries. Here is a table of simple data types and their widths in bits.
Type Description Bits
char A single byte 8
int A signed integer 16/32
float Floating point number 32
double Double precision float 64

Strange as this may sound, this is all. To make things easier, there are modifiers which change the width and format of the four data types. The modifiers are short, long, signed, and unsigned. The first two change the width and range of the data types; the latter two change between signed and unsigned formats. Here is a table of all the meaningful combinations of modifiers and data types:

Modified Type Bits Range
char
signed char
8 -128 ... 127
unsigned char 8 0 ... 255
int
signed int
16/32 -32768 ... 32767 or
-2147483649 ... 2147483648
unsigned int 16/32 0 ... 65535 or
0 ... 4294967295
short int
signed short int
8/16 -128 ... 127 or
-32768 ... 32767
unsigned short int 8/16 0 ... 255 or
0 ... 65535
long int
signed long int
32 -2147483649 ... 2147483648
unsigned long int 32 0 ... 4294967295
float 32 -3.4E-38 ... 3.4E+38
double 64 -1.7E-308 ... 1.7E+308
long double 64 -3.4E-4932 ... 1.1E+4932

As you can see, this gives a rather impressive collection. Note that there are defaults to each modifier. If you do not specify the data type, int is assumed (so it is more common to write long than long int). The default format is signed and the default size is normal (no modifier).

An important concept in C is that, although there are very concretely defined data types, you are not forced to obey them: you can store a value of 255 in a signed char. The compiler might warn you of possible problems, but will not generate an error. The interpretation of the bit patterns, however, depends on the variable's data type. So, when reading the signed char from the previous example, we will not get 255, but -1 (binary 11111111 = unsigned 255, but -1 in signed or twos' complement).

Another point (important to BASIC users) is that C does not allocate variables dynamically: you must declare them before use. The declaration is a line like one of the following:

char x, y;       /* x and y are chars */
int i;           /* i is an int */
float X;         /* note that x is NOT X */
long a=15;       /* variable initialisation */

Variable declarations are placed between an open curly bracket ('{') and the following statement, or outside function definitions, at the top level of the program. In the first case, they are local variables: accessible only within the block they were defined in (i.e. only accessible to the statements within the curly brackets). Variables defined at the top level are global: they are available to all your program.

To be continued.