Index


RISC World

Easy C++ Manual

Copyright © ProAction and APDL, 2001

Using Easy C++

EasyC Make

Easy C contains a powerful yet extremely easy-to-use Make facility which allows you to make a complete multi-file application all in one simple operation. A make facility is essential for large projects that consist of many source and header files, although it may be used with simple C programs too.

If you are developing a large application which uses a number of source files which in turn are dependant on a number of header files, it is very time consuming to re-compile all files after each change that you make. Clearly you need only recompile those files that have changed or whose dependants have changed. To do this manually is prone to mistakes e.g. you might change a header file but forget to recompile one of the source files that is dependant upon it.

The Make facility resolves all these problems by using a 'make file' which lists all the source files and their dependencies. You need only click on a button for it to make your program. Only those files that have changed or whose dependencies have changed (such as header files) are recompiled and then everything is linked. The Make facility recognises which files have changed by checking their date and time stamps.

Easy C has a facility to create a make file automatically for you. You need only tell it which source files are needed by your application, and it will work out the dependencies and create a make file.

Dhrystone

The best way to illustrate the use of the Make facility is to use a real example. On the Easy C disc in directory $.Programs is a directory called Dhrystone containing a popular test program for C compilers. This directory contains all the files associated with the Dhrystone project, including directory c containing the source files dhry_1 and dhry_2, and directory h containing the header file dhry. Both source files 'include' h.dhry, and are therefore dependent upon it.

Please note that you should create a new directory for each project that you wish to make e.g. Dhrystone. It should contain c, o and h directories and the make file for that project. Do not mix projects in a single directory.

Creating the 'make file'

To use the Make facility you must first create a 'make file' which defines all the files in the program and their dependencies. The Create option on the main dialogue box builds a make file for the files in the source window.

For the Dhrystone example, drag the source files c.dhry_1 and c.dhry_2 into the source window. If there are any files already in the source or object windows, remember to clear them by holding down Ctrl whilst dragging. Now click on Create to create the make file. When complete, the pathname of the make file is placed in the icon beneath the source and object windows.

The make file it creates is called Makefile and is placed in the same directory as the c and o directories. If a make file already exists in this directory, you will be asked for confirmation before it is overwritten.

Normally the object window should be empty when you create a make file. However, if your application needs to link in a separate object file, you should drag it into the object window before using Create.

Please note that when creating a make file, all source files must be in the same c directory, otherwise an error will be reported. Any additional object files you require, must be in the corresponding o directory.

Using Make

Before making a program, the icon to the left of the Make button must be set to the pathname of the make file. This is done icon automatically by the Create facility, but at the beginning of a session you must drag the make file into this icon (or onto the Easy C icon on the icon bar). Once the make file pathname has been specified, click on Make to make the program. Easy C will now compile and link those files that are needed to make the program.

Once Dhrystone has been 'made', if you try and make it a second time the message dhry_1 is up to date will be displayed. If you modify any of the source or header files, or delete the object or executable files, clicking on Make will intelligently recompile anything that has changed or is missing, and then link the program again. A quick way of testing this is to 'stamp' one of the source files using the Stamp option on the source window menu, then click on Make.

Make options

The Make option on the main Easy C menu displays a dialogue box from which the make facility may be configured.

Target

This is used to specify which target you wish to make. In a standard make file there is only one target, so this option can normally be ignored. However, it is possible to edit the make file directly and add other targets, which may be used to make special versions of the program. In this case you should enter the the target name in the icon provided.

Ignore

If this option is selected, the make facility will continue compiling and linking after any errors are reported.

Silent

This option controls whether the make facility echoes in the error window the commands that it generates to compile and link programs. If Silent is selected, the commands are not echoed.

Trace

If Trace is selected, the make facility echoes the compile and link commands tin the error window, but does not execute them. This feature is useful for checking that the make facility is generating the correct sequence of commands. The Silent option described above must not be selected for the Trace option to work.

Use setup

If this option is selected the make facility will use the settings from the Setup dialogue box. This means that the settings in this dialogue box will affect the make operation. For example, if List is selected in the Setup dialogue box, a listing will be generated during the compile part of the make operation. This is the default situation, and will be satisfactory for most purposes.

However, there may be situations where you do not wish to change the settings on the Setup dialogue box, but do want the make file to compile or link using a specified set of qualifiers. To do this you will need to de-select Use setup. Normally the setup options are passed to the make facility using the macros CFLAGS and LDFLAGS. If Use setup is not selected, these macros will be blank, so you should set them at the start of the make file with your own settings. For more information, see the sample make file given on the next page.

Make file format

The make file is a text file, so you may load it into a text editor for examination if you wish. Listed below is a commented version of the Dhrystone make file. In normal circumstances you do not need to understand this, but with a little knowledge you will be able to customise it for special applications.

# Lines starting with # are comments.
# Makefile generated by Easy C
#
# This makefile builds the following files:
#   c.dhry_1
#   c.dhry_2
# The next line defines a macro OBJS which lists all the 
# objects needed by the program. 
OBJS = o.dhry_1 o.dhry_2
# If c.dhry_1 and c.dhry_2 are modified, they are recompiled
# automatically using default rule cc $(CFLAGS) $* -desk.
# The macro CFLAGS is set by the Setup dialogue box, but may
# be redefined if required.
# The next line defines the target dhry_1 which consists of
# the objects specified by the OBJS. You may add other targets
# if you wish e.g. to make special versions of the program.
# The action needed to make the target is to link the objects.
# The macro LDFLAGS is set by the Setup dialogue box, but may 
# be redefined if required.
dhry_1 : $(OBJS)
         $(LINK) $(OBJS) $(LDFLAGS) -desk
# The next two lines state the the files dhry_1 and dhry_2 
# need to be recompiled if the header files are modified.
# The recompilation is done automatically by default rules.
o.dhry_1 : h.dhry
o.dhry_2 : h.dhry
#
# End of generated makefile, add other dependencies here
#

Note that the filenames in the make file are not specified by full pathnames, but only by the c, o and h directory name plus leafname. This is possible because all the files have the same root pathname. One case where this does not occur is header files from the Include path; in this situation, the full pathname must be given.

You may add commands to the make file to carry out special tasks, for example to delete the object files after linking. However, to do this you must tell the command line interpreter where the files are on disc. This is done using the special macro $(MPATH) which expands to the pathname of the directory containing the c and o directories. To delete all object files after linking, add this line after the link line and resave the make file:

wipe $(MPATH).o.* ~c

Please note that the Create facility does not built a dependency list for system headers (i.e. those in angle brackets <>) since they are not expected to change. If you do intend to change them, you should add these dependencies to the end of the make file.

We have yet to test the make facility fully with C++, and so would welcome any feedback (good or bad).

Let's take a look at a simple C program called HelloW:

/* HelloW - print hello, world */
#include <stdio.h>
int main(void)
}

There are several points to note here:

  C does not use line numbers.

  Lower case characters are used for C keywords and (conventionally) for user-defined words such as variable names and function names.

  A number of non-alphabetic characters such as curly braces (), hash sign (#) and asterisk (*) have special meanings in C.

Taking the program line by line, we find the following:

Line 1 in the source code above is enclosed in comment delimiters. Anything which appears between the /* and the */ is ignored by the compiler. Good programmers use this facility to explain how their code works.

Line 2 starts with the # character. This makes it a preprocessor directive, and is the first phase of the compilation. It tells the preprocessor to find a header file called stdio.h and treat it as part of this program. This file contains information relating to the standard input and output routines, and hence its name stdio. On finding this directive, the C compiler looks for a file stdio in directory h. This naming convention is the opposite way round to normal Acorn conventions, but allows portability between C compilers running under different operating systems.

Line 3 is the name of the function. All C programs must have a function called main(), and execution of the program starts here. The words int and void will be explained in due course.

Line 4 is an opening brace ( printf() is NOT a C command, but a call to a library function called printf(), which prints the string enclosed in quotes. The character pair \n represents 'new line', and note how this is also included within the quotes. Note also how the statement ends with a semi-colon.

Line 6 marks the end of the function, and in this case the end of the program.

This source code is used as input to the C compiler. The preprocessor copies in any #included files, and the expanded file is input to the compiler proper which will (compilation errors permitting) produce an object file which must be linked to produce a program. To run the compiled program, double-click on HelloW to display the message hello, world.

HelloW is a very simple program; it uses no variables and there is no flow control. The expanded program HelloW2 below, incorporates both of these features.

/* HelloW2 - hello, world expanded version */
#include <stdio.h>
int main(void)

  for (x = 0; x < 10; x = x + 1)
    printf("hello, world %d\n", x);
}

The first line after the opening brace, int x; defines a variable. All variables in C must be defined before they can be used, so that the compiler can reserve space for them in the object program, whereas in Basic, variables other than DIM declarations are called into being when they have a value assigned to them. The semi-colon terminates variable definitions as well as statements. As in Basic, variable types include int (integer, or whole number), float (floating point), and char (a single character). Strings are dealt with as 'arrays of type char'. Functions must also have a type according to the nature of the data they return, just as LEFT$() is a Basic string function, INSTR() returns an int, and SQR() a float.

The next line features a construction dear to any Basic programmer's heart - the FOR/NEXT loop.

The printf() function now has a more complicated structure than hitherto, containing as it does two arguments: the string "hello, world %d\n" known as the conversion string, and the variable x. The string contains literal text which is printed as it appears, plus formatting information to control the conversion (i.e. the format) of x. The %d tells the function to output its next argument as a decimal integer at this point on the printed line.

Unlike Basic's PRINT statement, printf() is a complicated beast and will be discussed in more detail as we proceed.

Iteration - the for loop

In C, for() works in very much the same way as the Basic equivalent, but is much more flexible. The general format is:

for ( expr1; expr2; expr3 ) statement

Note that no NEXT is required, and that statement could be a single statement which would then be followed by a semicolon, or a group of statements enclosed in braces. The brackets contain three expressions, separated by semi-colons: expr1 sets up the starting condition(s), expr2 is evaluated on each iteration, and expr3 is the 're-initialiser' which is performed after each iteration. The loop terminates when the second expression evaluates to FALSE (i.e. the loop is performed while the second expression is TRUE). The Basic equivalent of the above is:

FOR N%=0 TO 9 STEP 1
PRINT "hello, world "N%
NEXT

The loop is performed while x (or N%) is less than 10, hence TO 9 is its Basic equivalent. x and N% will both be 10 when the programs exit from their respective loops. However, unlike a FOR/NEXT loop in Basic, a for() loop in C will never be executed (even the first time) if the terminating condition expr2 is FALSE to start with. In C, expressions can be much more complicated than in Basic, for example:

for (x = 0, y = 1023; y >= 0 && point(x,y) == 0; x++, y--)

Here, there are two parts to the first and third expressions, and the central expression is a compound condition involving a call to a function. Note the lack of a terminating semi-colon; for() is a preamble to a statement or group of statements, rather than a statement in its own right. Let's look at this line in detail:

x = 0, y = 1023 assigns starting values to x and y. The comma separator may be used as both assignments formpart of expr1.

y >= 0 && point(x,y) == 0 can be expressed as 'while y is greater than or equal to 0 and the function point(x,y) returns the value 0'. The double ampersand && is the logical AND operator. In C, == serves as the equality test (IS equal to), as opposed to the single = (BECOMES equal to), for assigning values. The function call point(x,y) is evaluated after the test on y, and only if it is TRUE.

Lastly, x++, y-- means add 1 to x and subtract 1 from y (see below)

Optional parameters

While just the STEP clause is optional in Basic, everything is optional in for(). If you examine a few C programs you will often see 'empty' for() loops, where all the work is done in the 'reinitialise' expression, for example:

for ( m = 1; n > 0; m *= n--)
  ;

For clarity, the body of the loop, namely the semicolon which must be present, is detached from the closing parenthesis. Similarly, each of the three expressions of for() in parentheses is optional:

for( ; m < 10; ) /* no initialise or reinitialise */
for( ; ; )
  /* start of an infinite loop */

Incrementing and decrementing

Incrementing and decrementing variables by one occurs so often that it gets its own operators; ++ and --. They can go before the variable (prefix) or after it (postfix). Prefix increment means 'add 1 to the current value, then use it'; postfix means 'use the current value then increment it'. For example:

x = 7;
  /* x is initialised */
y = 4 * ++x;
  /* x is incremented first, so y = 32 */
z = y--;
  /* z becomes 32, y becomes 31 */

Assignment operators

The notation *= used above may be familiar to Basic V programmers who can use the related += and -=. In general, the notation:

A <op>= B

expands to:

A = A <op> B

where <op> can be almost any binary (two-operand) operation.

The root of the problem

This section describes a C program to calculate the square root of a number. We present the program here in its entirety before discussing its individual features. In particular, the example illustrates the use of some of C's flow control commands, and the use of variables.

/* Root - calculate the square root of a number */

#include <stdio.h>
#define ACCURACY 1.0E-6
double sqt(float);
int main(void)

  printf("give me a number; ");
  scanf("%f", &n);
  while(n != 0)
  
    printf(" square = %f", n*n);
    if(n > 0)
      printf(" root = %f \n", sqt(n));
    else
      printf("Negative - no real root\n");
    printf("give me a number; ");
    scanf("%f", &n);
  }
}


double sqt(float num)

  acc=ans=num/2.0;
  while(ans > 0 && acc > ACCURACY) 
  
    acc=num - (ans*ans);
    if(acc < 0)
      acc = -acc;
  }
  return ans;
}

The first line is comment, the second is the #include preprocessor command we saw earlier, but the third line is new. It is another preprocessor command, #define which causes all occurrences of the first word (throughout the source program) to be expanded to the value of the rest of the line. This is useful for large programs using a constant value in many places which is subject to change, such as VAT rate, currency symbol, and so on. If the value changes, you only need to change it in one place in the program. Here we use it to define the accuracy required in our calculation. The notation might be familiar to you; it means '1.0 times 10 to the -6th power', or 0.000001.

The fourth line is a function prototype; it tells the compiler that the function sqt() exists in the program which returns a value of type double, and takes one parameter of type float. Double stands for 'double precision floating point'. Specifying a prototype allows the compiler to check that all calls to the function have the right number and type of parameters. For the purpose of this example the function has been called sqt() to avoid a name clash with the function sqrt(), which is supplied as standard with all implementations of C.

As a function itself, main() has a return type of int (normally for returning an integer error code). It has no parameters, so you must put void where the arguments would normally go.

After the opening brace which marks the start of the program proper, the variable n of type float is defined. This is followed by a simple printf() to ask the user for input, and a call to scanf(), which is the C equivalent of Basic's INPUT. scanf() takes as its arguments a conversion string (%f in the above example) and the address(es) of where the inputted data is to be placed; &n means 'the address of n'. We'll be looking at addresses and pointers in detail later. Strictly speaking, scanf() fetches the data from the standard input stream, but this will normally be the keyboard.

Then a new iterative construct called while(), which is similar to WHILE in Basic. The expression in brackets after is evaluated and if it is TRUE (or non-zero) the group of statements is executed. This process repeats until the expression evaluates as FALSE (or zero). The line while(n != 0) means while n is NOT equal to 0, so the loop is only executed if a nonzero value is entered.

On the next two lines, the value entered and its square are printed. Then the value of n is tested using the if() statement. It works much like the Basic equivalent; if the expression evaluates as TRUE (non-zero), the statement (or group of statements) is executed, otherwise the statement(s) following the optional else are executed. Processing continues from the statement after the else statement. In this case, if n is negative, a message is printed, otherwise we print the value returned by the function sqt() with n as its parameter (%f is the floating point equivalent of %d, by the way). To evaluate this, C passes a copy of the value held in n (call by value, an important property of C), executes the statements in sqt() and comes back with the calculated answer. The loop ends with another prompt for input, and processing continues until the user enters zero. The program terminates at the closing brace of main().

sqt() is a separate function, and starts as all functions must, with its return type, its name, and any parameter declarations (naturally, the definition of a function must agree with its prototype at the start of the program). In this case the return type is double, its name is sqt(), and its one parameter num is of type float. In our example, num will contain a copy of n. At the start of sqt() some more variables are defined and initialised. These variables are local to the function, and are called into existence for the duration of each call. The variable ans, for example, doesn't exist as far as main() is concerned. Nor does it retain its value between calls. Note the technique for assigning a value to more than one variable.

The first statement of sqt() is another while loop. In this case, the loop will repeat as long as ans is greater than zero AND our accuracy requirements are not met (note the && for the logical AND in C, as opposed to the single & for bitwise AND). The test for zero is to avoid a divide by zero within the loop. Unlike Basic, C can't trap this for you tidily. At the end of the loop, acc is made positive, in an operation equivalent to Basic's ABS function.

The sqt() function uses an iterative method for obtaining a root, and this consists of refining a guess at the square root of num until the required accuracy is attained. Try out the arithmetic with a calculator if you like; the arithmetic operators and brackets within the loop mean the same as in Basic. When either condition is satisfied, the function sends the answer back with return(ans). The value so obtained is passed to printf() for display upon the screen.

If you compile and run the program you will find a limitation C shares with Basic; floating point numbers are held to an accuracy of about nine digits, which causes inaccuracies in the least significant digits of large numbers. This may be avoided by checking that the user's input is both positive and less than a certain threshold, by changing the if statement in main() to read:

if(n > 0 && n <= MAXNUM)
  printf(" root = %f \n", sqt(n));
else
  printf("Negative or too big - can't do\n");

Where MAXNUM could be #defined like ACCURACY. You may be wondering why #define is used. Using 'magic numbers' in the source is bad for portability, as MAXNUM depends on the implementation and might be different on another machine. Hence we set these things out at the top of our program, or even in a header file. On another machine or with another compiler we could either amend the source or use the 'local' version of the header file.

We'll return to sqt in the future, and refine it to reduce its limitations.

Character handling

In Basic, there is a variable type string which holds 0 to 255 characters, preceded by a byte to hold the variable's length. In C, as we mentioned earlier, strings are dealt with as arrays of characters. The end of the string is marked by the character whose ASCII code is 0 (null). This simplifies things in some ways, while complicating them in others. In C, a variable of type char is defined as being at least big enough to hold any member of the machine's character set. The definition carefully avoids tying a char to a byte, and means you shouldn't make such an assumption in your programs. Since it is possible to do arithmetic on char's, it means we can do away with the basic functions ASC and CHR$ at a stroke. Look at the following Basic fragment, and compare it with the C fragment which follows it:

REM Basic put "B" in new$
num1%=ASC("A"): num2%=num1%+1
new$=STR$(num2%)

/* C put "B" in new */
int num1, num2;
char new;
num1='A'; 
num2=++num1;
new=num2;

In the C program, 'A' is a character constant, defined as an int with the numeric value of the quoted character in the machine's character set. As a matter of fact, the use of int above is superfluous, given that arithmetic can be performed on char's. A C programmer might write:

/* put "B" in new - version 2 */
char new='A';
new++;

This sort of thing won't be completely new to you if you have used indirection in Basic.

It could be said that C's characters operate in much the same way as Basic's byte indirection while strings, being arrays of characters, are like Basic's $ indirection. This is where an area is set aside in the program to hold (in our case) a string:

DIM mess 50       : REM reserve 50 bytes for string
$mess="Hello mum" : REM insert data
PRINT $mess       : REM behaves like a string variable
mess?5=13         : REM insert return at 6th char
PRINT $mess       : REM result would be "Hello"

In C, the code looks quite similar:

char str[50];             /* reserve 50 chars */
strcpy(str, "Hello mum"); /* copy message in */
printf("%s\n", str);      /* print our message */
str[5]='\0';              /* insert null character */
printf("%s\n", str);      /* print result, "Hello" */

The first line of the C example defines an array called str with 50 characters. Note that C uses square brackets for array subscripts. The second uses a library function strcpy to transfer the string into our array. Just as Basic adds a return character, C adds a terminating null character to the source string Hello mum which is copied into str. The third line prints it; %s is the conversion code for strings, and \n adds the newline. Line 4 changes the contents of the sixth character (C arrays start at element 0). \0 is a special character constant, representing zero. Finally we print the altered string.

The nth element of an array is accessed as str[n-1]. However, there is another useful notation which exploits one of C's most powerful tools. C is a high-level language which allows complicated operations to be programmed in a small amount of human-readable code. However, it shares some characteristics with assembly languages, one of which is the ability to use the address of something, as well as its value. The address of an array is held in a special variable called a pointer. Pointers, like ordinary variables, must have types; in the example above the type is char. In fact, str itself is a pointer which holds the address of str[0]. The address of str[1] may be expressed as str+1, and so on.

The arguments passed to printf() and strcpy() are actually pointers to char. You can declare free-standing pointers like this:

int n;   /* ordinary variable */
int *ip; /* pointer to int */

Pointers can hold the address of any variable of the same type:

ip = &n;              /* ip points to n,
                         & means 'the address of' */

You can then use the variable name and the pointer interchangeably:

*ip = 5;              /* the same as 'n = 5;'
                         '*' means the "value at the location pointed to by" */
nsquared= *ip * *ip;  /* multiply 5 times 5 */
printf("%d", *ip);    /* print 5 */

Using the new notation with an array is quite logical:

str[5]='\0';

is equivalent to:

*(str + 5) = '\0';

The brackets are needed to force the expression on the left of the equals sign to evaluate correctly; *str + 5 is 'the contents of str[0], with 5 added' or in the above example M (the ASCII value of the H of Hello plus 5). You can do a limited amount of arithmetic with pointers. Remembering that a string is a zero-terminated character array, the following fragment will print the length of the string in str:

char str[512];
char *temp;                     /* pointer to char */
scanf("%s", str);               /* str is the address of */
for(temp = str; *temp; temp++)  /*the start of the array */
  ;
printf("this string is %d chars long\n", temp - str);

The for loop is empty because all its work is done in the re-initialiser. The loop will exit when the central expression *temp evaluates to zero i.e. when the character pointed to by temp is NULL, which is at the end of the string. The expression temp++ simply increments temp so it points to the next element in the array.

The only legal arithmetic operation involving two pointers is subtraction and the result is only defined when they point to the same array. The result of such a subtraction is the number of elements lying between the two pointers, plus 1. So if p1 and p2 point to adjacent elements in an array, the expression p2 - p1 evaluates to 1.

Pointers and arrays

One of the first things often to strike the newcomer to C about arrays is the use of square brackets for the subscripts, for example when declaring an array:

int two_dim [12] [31];

Secondly, unlike Basic, Fortran, Cobol and so on, each subscript has its own pair of brackets. This allows us to think of the array as (in this case) twelve separate arrays, each of 31 integers. The following code illustrates one use of this concept:

int monthtotal(int daily_figs[])

  for(n = 0, total = 0; n < 31; n++)
  total += daily_figs[n];
  return total;
}

monthtotal() simply returns the sum of all the integers in a 31-element array; elements 0 to 30. There is no element 31! The array size is not required in the function definition as it is defined elsewhere. The function monthtotal() works quite happily in all the following cases:

int jan_figures[31];
int year_1990 [12] [31];
int decade_80_89[10] [12] [31];
jan_total=monthtotal(jan_figures);
for(n = 0; n < 12; n++)
  printf("Month %d total = %d\n", n, monthtotal(year_1990[n]));
for(y = 0; y < 10; y++)

  for(n=0; n<12; n++)
    printf("Month %d total = %d\n", n,
              monthtotal(decade_80_89[y][n]));
}

Notice how we send a 31-element array to the function by omitting the last set of brackets. As we suggested last time, a (one-dimensional) array name on its own is a pointer to the first element in the array. The designers of C are very fond of recursive definitions, so an n-dimensional array is the same as an (n-1)-dimensional array of one-dimensional arrays, and therefore year_1990[n] is a pointer to an array, as is decade_80_89[y][n].

In passing, you may have spotted the arithmetic expression 1980 + y in one printf() statement. As with Basic's PRINT expression arguments are evaluated before the call to printf() actually takes place. In general, wherever C expects an object, it will be quite happy with an expression which has a result of that type.

The function monthtotal above could be made more useful if it didn't always have to add 31 elements. A more generally useful version would add as many elements as we liked, any time we called it, and I suspect it would use pointer notation. Before we start, however, some design considerations.

How do we specify which elements to add? The array could terminate with an agreed end-of-array value, just as strings always end in the NULL character. This would be awkward, as almost any value might reasonably appear in an integer array. One could use the implementation-defined maximum integer value, INT_MAX which is #defined in limits.h, but what if you want to add a part total, for example a weekly total within the monthly figures above? You would have to insert INT_MAX in your array, call the function and then restore the original value. A second approach is to specify how many elements to add, passing this as an argument to the function. This imposes fewer restrictions on the input, and is probably the best solution for our purposes. Observe:

/* arraytotal - accumulate n elements from an array of ints */
int arraytotal(int *a, int n)

  while( n-- > 0)
  total += *(a++);
  return total;
}

The function definition now contains two arguments, of which the first is the more interesting. The argument is of type 'pointer to int', and will contain a copy of the address of the first element in the array. The second contains the number of elements to be accumulated. Since these are copies internal to the function, we can alter them safely in the while loop. As each element is added to total, a is incremented. This causes a to point to the next element in the array (not the next byte). n is decremented until it reaches 0 at which time the required total has been computed, so the function returns.

Calling the function is done in exactly the same fashion as before, except that we must specify the number of elements to add:

jan_total=arraytotal(jan_figures, 31);
for(n=0; n<12; n++)
  printf("Month %d total = %d\n", n,
            arraytotal(year_1990[n], 31));
week1_figs = arraytotal(jan_figures, 7);

Of course, we need not start at the beginning of an array:

week2_figs = arraytotal(jan_figures + 7, 7);

And we can treat a two-dimensional array as a one-dimensional array:

year_total = arraytotal(year_1990, 31*12);

although this seems like bad programming as it relies on the 12 31-element arrays being contiguous in memory.

Files

Just like any other programming language, C has a set of functions allowing the user to open, read, write and close files.

A file is viewed as a stream of characters, hence my references to the standard input stream and so earlier in the chapter. All input and output is actually done through streams. When a C program is run, three streams are immediately opened - the standard input (stdin), standard output (stdout) and standard error ( stderr) streams. stdin is normally assigned to the keyboard, stdout and stderr to the screen, but can be redirected, for example, to take input from a file. More of this later.

The Basic programmer will feel almost at home with the fopen() command:

fp = fopen(filename, mode);

fopen() is a function which opens a stream and assigns a file to it. filename is a character pointer to the string containing the filename, and mode is a character pointer to a string denoting the required access, such as r for read, w for write (create a file or open and clear an existing file), and a for append (create or open for writing at EOF; end-of-file). To these may be added + denoting read/write access, and/or b for binary files (as opposed to text files which, in some environments, are treated differently). For example:

infile = fopen("$.scores", "r+");       /* open file '$.scores' for update */

fopen() returns a pointer of type FILE:

FILE *infile;

which is typically a collection of data about the file including its name, status, location and so on. fopen() sets this data up and infile points to it, in much the same way as Basic's file handle. All other file-handling functions use the file pointer to access the file. If the file cannot be opened, the file pointer is set to NULL (zero). By definition a NULL pointer does not point to anything valid. Further information about the error is put in a variable errno declared in the header file errno.h.

Once the file has been opened, there are a number of ways of getting data into and out of it, including:

fgetc(), fscanf(), fread(), fgets()  various input functions
fputc(), fprintf(), fwrite(), fputs()  various output functions
ftell(), fseek(), rewind()  manipulate file pointer

Briefly, fprintf() and fscanf() work exactly like printf() and scanf(), except that they have an extra argument, a file pointer; fread() and fwrite() handle multiple reads and writes into/out of arrays; fgets() and fputs() read and write strings. For now we'll concentrate on fgetc() and fputc().

fgetc() and fputc() are very much like BGET# and BPUT#, which you'll no doubt have come across in your Basic programming. We're going to use them to read and write strings in Basic file format. This will be useful if, like me, you have huge amounts of data generated by Basic programs and your future development strategy involves C.

Back to basics first. In Basic, as we have already discussed, a string consists of zero to 255 bytes preceded by two other bytes. The Basic file format reflects this:

Byte    0  1
  
2
  
3
  
4
  
5
  
6
  
7
  
8
  
9

Hex      00    08    64    72    61    77    6B    63    61    42

Alpha    -    -    d    r    a    w    k    c    a    B

First of all, there is a zero byte - indicating a string - followed by a count of the number of characters in the string, and then the string itself. For reasons which will soon become clear, the string is held in reverse order.

The object of the first exercise is to write a function which will read a string from a file into a character array. The sequence of operations is as follows:

1.  Fetch the string length, l.

2.   Fetch l characters into our array, last one first.

3.  Terminate said array with a null character.

For now we'll assume the file is opened and closed in another function such as main().

/* read_bstring - read a string from a Basic-format file and place in str */
void read_bstring(FILE *in, char *str)

  n=fgetc(in);  /* zero */
  n=fgetc(in);  /* string length */
  str[n--]='\0'; /* insert terminator */
  while(n >= 0)
  *(str + n--) = (char)fgetc(in);
}

As you can see, reverse storage and using n as an offset which decrements to zero lends itself to more efficient code. As a result, str will contain the data from the file, and the file pointer will be positioned in the file ready to read the next byte. Note the function type void, signifying no return data.

Of course, no physical operation such as reading files can be foolproof; faulty discs and corrupt data must be catered for. The function feof() returns TRUE if end-of-file is reached, and the function ferror() returns TRUE if a file error has occurred. These could be incorporated into the while statement thus:

while(n >= 0 && !feof(in) && !ferror(in))

Where && is AND and ! is NOT. It would be better to indicate to the calling function whether there has been any trouble. This could be achieved by returning the exit value of n (-1 if all is well) or by returning zero on successful termination, or by returning the number of bytes read i.e. the length of the string. This is largely a matter of taste; there is no real standard in operation among the other C I/O operations. The closest equivalent is fgets(), which returns a character pointer to the fetched string if OK, or a null pointer if unsuccessful. This would appear at the end of our source as:

if(n == -1)   /** valid exit from 'while':
  return str;
else
  return 0;

and the function type must be changed from void to char *.

Here is a complete program Bstring, which alls read_bstring():

/* Bstring - read and display a file full of Basic-format strings */
#include <stdio.h>
#include <stdlib.h>
char *read_bstring(FILE *, char *);
int main()

 char b_string[256];
 in=fopen("Bstrings", "r");
 if(!in)
   exit(1);
 }
 while(read_bstring(in, b_string))
 printf("%s\n", b_string);
 if(!feof(in))
   printf("Some sort of file error.");
 fclose(in);
}

Not much here you haven't seen before. I've #included stdlib.h because that contains the prototype for exit(). The function prototype doesn't need variable names, just types (this can look odd with pointer variables), and you don't need function prototypes if all functions are defined before use i.e. no forward references. This can make your programs look odd, with main() at the bottom, but they'll work just as well and you won't need to keep the prototypes in line with the functions - a real problem when developing large programs. However, prototypes serve to document your program, so you should consider putting them in when the number and type of functions (and their arguments) settle down.

The only other odd lines are the if after fopen() and the while beneath it. Both of these evaluate something which will be NULL if an error has occurred; the if reads as 'if file-pointer in is not set', and the while reads as 'while read_bstring returns a non-null value'. This is quite common in C programs.

Introducing structures

An important feature of most powerful programming languages, but sadly absent from Basic, is the facility to handle user-defined data structures. A structure is a collection of variables which relate to one another in some fashion. A classic example is the payroll record - name, hourly rate, tax code, etc. Each part of a structure can itself be a structure - the tax code consists of an int and a char, for example. The only restriction is that a structure cannot contain a structure of the same type. C provides mechanisms for handling structures which enable you to handle large chunks of related data as a single entity.

First of all, the notation: to define a structure you simply list all the constituent parts in braces, like this:

struct example 
  char address[4][30];
  int age;
  float height, weight;
  };

Note the semicolon following the closing brace. This is necessary because the braces form part of the struct declaration. When the compiler meets this, it does NOT reserve any space; it has simply been told about a new data type called example (in this instance). Later in the same program, you might define a variable thus:

struct example details;

This is the point when space is reserved for a variable called details of type example.details contains enough space for all the individual data items. To access these items, you must use an expression of the form:

details.age

The full stop is known as the structure member operator. Here are some examples:

details.age = this_year - year_of_birth;
strcpy(details.name, "Peter Pan");
scanf("%s", details.address[i]);

In general, a structure member can be treated as a simple variable of the same type.

Structures are often used in arrays. The notation is a logical extension of the simple arrays we've already met:

struct example details[10];     /* defines 10 structures */
details[n].age = days_old / 365;
scanf("%s", details[n].address[i]);

As you can see, the subscript follows the structure name, and is itself followed by the rest of the member name. A common cause of compilation errors is misplacement of subscripts in complex structures.

Naturally enough, you can also use pointers to access structures. This leads to some fun and games in the notation department:

struct example details[10], *ex;  /* define an array and a pointer */
ex = &details[n];
(*ex).weight = weight_in_ounces / 16;

You can substitute *ex wherever details[n] can occur, but brackets are necessary here because the structure member operator (.) has a higher precedence than the indirection operator (*). The expression *ex.weight means 'the pointer weight within the structure ex'. Again, this is a rich source of programmer error, which is a shame because this is an immensely useful aspect of C.

To get round this a special operator, known as the structure pointer operator is used. In our example:

(*ex).weight

could be written as

ex->weight 

The new operator is represented by a minus sign followed by a right angle bracket.

Naturally, you can have pointers within a structure, but what surprises many people is that a structure can contain pointers to the structure itself. One important application of this idea is in the linked list. In essence, the structure has three components:

struct line_data
  struct line_data *prev; /* pointer to prev line */
  struct line_data *next; /* pointer to next line */
  };

Insertion and deletion are handled by linking and unlinking pointers. If p points to a line we wish to delete, the two instructions:

p->prev->next = p->next; 
p->next->prev = p->prev;

achieve this (the first and last lines require slightly different treatment). It works because p->prev is a pointer to the line_data structure just as p is, so p->prev->next is 'the pointer to the line after the previous one', which should be equal to p itself. The first line's prev would be set to NULL, as would the last line's next pointer; by definition, any pointer with a value of NULL doesn't point to anything (NULL, by the way, is a symbolic constant #defined in stdio.h). To present the data in order, we must know where the first line is:

struct line_data *first;   /* set and maintained
                              during the program */

or we could search from the current position until we find the first line:

while(p->prev != NULL)
  p = p->prev;
p=first;
do
  p = p->next;
  }
while(p != NULL);

This simple technique is used in many sophisticated word processors and editors. It can be adapted to maintain a sorted list by pointing to the next larger and next smaller items. The name/height/weight example above could be adapted as follows:

struct example 
  char address[4][30];
  int age;
  float height, weight;
  struct example *older, *younger;
  };

As data is captured, its place in the list is found by recursive comparisons with older�>age and younger->age. This leads to a tree structure which is discussed in detail in Kernighan and Ritchie's book, The C Programming Language.

Formatted input and output

C is supplied with a whole family of input and output routines, several of which have already been used quite extensively. Two of the most important functions (printf() and scanf()) have already been discussed briefly, but this section describes them in more detail and covers other functions in the same family. A full technical description of these functions is given in chapter 5.

The functions under discussion consists of fprintf() and fscanf(), which transfer data to and from files, and sprintf() and sscanf(), which format data within the program's memory. Variable argument variants of these (vprintf() and so on), are also available, but will not be discussed here.

In general, printf(), scanf() etc. take a variable number of parameters one of which is known as a format control string. This effectively tells the function how many arguments have been passed to it, and controls the way in which they are processed. Because of this, its important that the number and type of arguments agrees with the format string, or dire consequences may result. The prototypes for these functions are:

int printf(char *format, ...);
int fprintf(FILE *fp, char *format, ...);
int sprintf(char *str, char *format, ...);
int scanf(char *format, ...);
int fscanf(FILE *fp, char *format, ...); 
int sscanf(char *str, char *format, ...);

The ellipsis (...) indicates that zero or more arguments follow. The character array format can be a quoted string, as used in previous instalments, or it can be a variable.The functions are all of type int. The printf() group returns the number of characters output (in the case of sprintf(), excluding the terminating \0). The scanf() group returns the number of input items assigned.

The format string may contain any sequence of characters. These are simply copied to the output stream until a % or \ is encountered. The backslash introduces an escape sequence - an otherwise unprintable character, such as Newline (\n), Formfeed (\f), and so on. The percent sign marks the beginning of a conversion string. When printf() encounters a % it converts the next argument according to the characters following the percent sign. You've already met %s for strings (strictly, pointers of type char) and %d for decimal integers.

Other conversion types are:

Character  Meaning

x    Hexadecimal integer, using lower case a - fX    Hexadecimal integer, using upper case A - Ff    Floating point number e    Floating point number in the form n.n...nEmm g    Select %f or %e whichever gives the shortest output %    Print a percent sign

The full list will be found in chapter 5. While the percent sign marks the beginning of the conversion, the letter marks the end. Between the two you can specify various other characteristics of the output such as field width, accuracy, zero suppression and so on. The general format of a conversion string is %-n.mlx where;

n is the minimum field width. If nhas a leading zero, zero suppression is NOT performed (strictly speaking, the field is padded with zeros rather than spaces);

m is the precision (for floating point numbers) or the maximum number of characters to be printed from a string, or the minimum number of digits for an integer;

l is the lower-case letter L, used to signify that the variable is of type long (when used with ints) or double (when used with floats).

x is the conversion character as above.

The optional minus sign indicates left justification of the converted value. Even strings are usually right-justified within their field!

Here are a few examples; each group is preceded by the definition of the variable printed. The colons show the exact size of the field, an idea I've borrowed from the standard work on the language, The C Programming Language by Kernighan and Ritchie.

int x = 13579;

Conversion string    Result

1.:%d:        :13579: 2.:%06d:      :013579: 3.:%-8.3d:      :13579 : 4.:%08.13d:      :0000000013579:

Line 1 shows the default format. In line 2 the minimum field size is specified and zero suppression is cancelled. The field is left justified in line 3, and line 4 shows how the precision overrides the minimum field width.

float one_seventh = 100000.0 / 7.0

Conversion string    Result

1. :%f:        :14285.714286: 2. :%06.9f:      :14285.714285714: 3. :%9.12f:      :14285.714285714286: 4. :%-29.22f:    :14285.7142857142862000000000 : 5. :%.4f:      :14285.7143:

The definition requires explanation here. The numeric constants must have the decimal point otherwise C treats the division as of type int, which would yield exactly 14285 - the result is truncated, not rounded. Note how the field in line 4 is 29 characters wide including the decimal point and the trailing space, and that rounding does take place at line 5.

The answers are accurate to 12 decimal places (a total of 17 significant figures) which is sufficient for most purposes. The symbolic constants FLT_DIG, DBL_DIG and LDBL_DIG, #defined in float.h, give the 'digits of precision' that can be relied on for types float, double and long double respectively, which allows you to check the value prevailing at run time, and act accordingly. In Easy C they are all reliable to 15 significant figures.

char str[13] = "Twelve chars";

Conversion string    Result

1. :%s:        :Twelve chars: 2. :%15.10s:  :   Twelve cha: 3. :%-15.10s:    :Twelve cha : 4. :%10s:      :Twelve chars: 5. :%.10s:      :Twelve cha:

Note the right justification at line 2, and the difference between line 4 (where the minimum field width is specified) and line 5, where the 'accuracy' is specified. If you want to truncate a string from the left (giving elven chars, for example) you should pass the appropriate pointer to printf() - in this case (str+2).

The fact that C will not truncate a numeric field to a specified size is both a blessing and a curse; it can play havoc with tables of figures, for example, but it does mean that no data is lost. It is possible to align columns by exploiting the fact that printf() returns the number of characters it has output, and another conversion character, *, which may appear either in the width or precision area. This returns the value of the next argument which is then used for the specification. The examples above were tabulated using this facility.

The left-hand part was printed thus:

x=printf("%2d :%s:", n, fmt);
/* n=1 to 5, converted by '%2d'
fmt= (for example) "%10s" converted by '%s' */

After this call to printf(), x contains the number of characters output. The right-hand part followed:

printf("%*s:", 20-x, " ");
/* the correct number of spaces */
printf(fmt, str);
/* str contains "Twelve chars"  */
printf(":\n");
/* end of the line              */

The first printf() outputs a field exactly 20-x characters wide, which contains a single space (right-justified, if anyone cares). Note how the variable fmt has been used both as a format control string and as a variable - the poacher turned gamekeeper!

Here's a routine which exploits the variability of the field width. The object was to print totals held in the array total[ ] at the foot of columns of figures; the offset of the right-most digit in each column was stored in array wd[ ]. max_n holds the highest subscript used in the arrays.

for(n=max_n; n >= 0; n-)
  printf("%*d\r", wid[n], total[n]);

This prints the rightmost total first, space filled to the left. The Escape sequence \r denotes Carriage Return, without Line Feed. This allows the totals to appear on the same line.

Formatted input - scanf()

The formatted input command scanf() works along broadly similar lines to printf() . The major difference is that the arguments to scanf() must be pointers to the variables which will hold the data. This is a good place to start looking if your C programs don't work! Almost everyone forgets sooner or later. The address of a variable var is given by &var or by a pointer to which the address has been assigned. We could have used scanf() in write_bstring() in the previous section; the inner do loop would be replaced by:

scanf("%s", &bstring[0]);

Remembering that the name of an array is a pointer to element 0, this could be written:

scanf("%s", bstring);

One disadvantage of scanf() is that any 'white space' character (the general term for Space, Tab, Vertical Tab, Formfeed, Carriage Return and Newline) terminates a conversion. This means that write_bstring would write output one word at a time.

Any non-conversion characters (such as the colons in the printf() examples above) must be matched on the input stream. If such a match fails scanf() terminates. This can obviously cause problems with keyboard input.

scanf() returns the number of items successfully matched and assigned. If end-of-file is reached during a conversion, scanf() returns the value EOF - this is #defined in stdio.h as a value which cannot be confused with a character or item count (usually -1, but test for EOF for portability!)

Why should you expect EOF in keyboard input? In the first place, many operating systems allow the input stream to be redirected - that is, input may be coming from a file (see below). Naturally, when the input file is exhausted you would want the program to be able to detect it. Secondly, you can simulate EOF at the keyboard by entering Ctrl-D. This also works on Unix and MS-DOS by the way. Finally, this allows the whole scanf() 'family' to behave in similar ways under analogous circumstances - the designers of C have sought everywhere to exclude special cases as far as possible.

Redirecting input and output

A remarkably useful feature of C programs is their ability to redirect their input and output. This facility is borrowed from UNIX and MS-DOS and their obsolete forebears. Essentially, a program which is written to take input from the keyboard and output to the screen can take input from a file, write its output to a file or both. The syntax will be familiar to users of the above-mentioned operating systems. To run a C program called myprog, using input stored in keystrokes and writing output to printout, you would type the following at the command line:

*myprog <keystrokes >printout

The < controls redirection of the standard input stream (stdin), while the > controls the standard output stream (stdout). What about the standard error stream (stderr)? In the present case, any output sent to the standard error stream would still appear on the screen. To send the error stream to a file called errors, you would type the following:

*myprog <keystrokes >printout 2>errors

The basis for this notation lies in the conception of stdin as stream 0, stdout as stream 1 and stderr as stream 2. The zero and one are not usually specified though you can do so if you like. To redirect stdout and stderr to the same file, you could type:

*myprog 0<keystrokes 1>printout 2>&1

or in a shorter form:

*myprog 0<keystrokes >&printout

which means 'send stream 2 to wherever stream 1 is going'. If the 2>&1 appeared before the >printout the stderr output would still appear on the screen as at that point that is the current destination of stdout.

It is useful, when developing programs, to have some way of following progress on screen, so I would not recommend redirecting stderr until you are satisfied your program works properly, or unless you want to preserve the output for a later postmortem.

Command line arguments

A popular way of invoking programs, of the sort you'll be creating initially, is:

*myprog arg1 arg2

This would cause the program myprog to be loaded and run (provided it existed in the current directory or the library). arg1 and arg2 are 'command line arguments', which are passed to the program by the operating system. But how does the program get hold of them?

Up to now, when example programs have appeared in this tutorial, the function main() has been followed by empty parentheses or the word void. However, main() can have two arguments. The first is an argument count, the second an array of char pointers, each pointing to a word on the command line. By convention, these two arguments are known as argc and argv respectively. The full prototype of main now reads:

<type> main(int argc, char *argv[])

where <type> would be void or int or whatever. The second argument, by the way, reads 'an array argv[] of type char *'. Mastering the art of interpreting complex declarations is an achievement of no small difficulty - take my advice and stick to the simple ones wherever possible.

argc will always be at least 1, in the case where the only word on the command line is the program name. Thus, the contents of argv[0] will always be the program name as entered at the keyboard. This allows you to detect if the directory path was used, if you really want to know (and I dare say there's scope for a spot of software protection here). A word is defined as a string of characters bounded by unquoted white space. Thus:

myprog hello mum  /* argc = 3 */

but:

myprog "hello mum"  /* argc = 2 */

Successive entries in argv[] will point to successive words on the command line. As with most things, C trusts the programmer to stay within the bounds of the array. And here we come to another rich source of errors in C programs; the number of arguments after the command line and subsequent program name (held in argv[0]) is thus one less than argc as : the first effective argument is argv[1] and the last is argv[argc-1].

Here's a brief example of a program which takes arguments:

/* Calc.c  - prints the result of arithmetic */
/* expression in arguments 1-3 e.g. 75 / 15 */
/* Written by Dec McSweeney 1991 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])

if(argc != 4)
   exit(1);
   }
op1 = atoi(argv[1]);
   /* function atoi(str) returns int value of string */
op2 = atoi(argv[3]);
switch(*argv[2])
      printf("%d\n", op1 + op2);
      break;
   case '-':
      printf("%d\n", op1 - op2);
      break;
   case '*':
      printf("%d\n", op1 * op2);
      break;
   case '/':
      printf("%d\n", op1 / op2);
      break;
   default:
      fprintf(stderr, "Unknown operation");
      break;
   }
}

Note how the switch statement uses *argv[2], that is, the character pointed to by argv[2] (each entry in the array argv[] is a pointer of type char remember), while atoi() takes as arguments the pointers themselves.

The arguments above are positional, that is they must appear in a fixed sequence and their meaning to the program is indicated by their position. More normally, at least some of the arguments will not be order-dependent and in the case of a program with many options could be quite numerous - think about writing a C version of the RISC OS *COPY command.

The usual way to handle such a situation is to process each argument in turn using a for loop:

for(n=1; n<argc; n++)
   }

Observe that n starts at 1, but the terminating condition is n<argc, so the last iteration processes argv[argc-1]. For a command like *COPY the first two arguments would be positional (and compulsory), but the remainder could appear in any order. In such a case the program would validate for at least two arguments ( argc >= 3) and the for loop would start at the first optional argument. Optional parameters should be set to their defaults first, then reset if mentioned in the command invocation. Mandatory parameters can be set to invalid values first, then checked for valid values after all the arguments have been processed.

Some useful functions

The standard C library contains a large number of useful functions. Many, such as the input and output routines, have already been discussed. One group of functions that has not been covered is concerned with the dynamic allocation of memory.

In a multi-user operating system such as UNIX, or a multi-program environment such as RISC OS, it is important that programs do not take up more memory than necessary. If your word processor is to be used for a short letter, it doesn't need enough memory to store a novel. In the same way, it is frustrating if a program imposes unnecessary limitations on the user. One popular word processor runs on the IBM PC (a 640K machine), but will only cope with documents of up to 32K!

The easiest way to cope with this problem is to provide the program with memory when it needs it, and to claim it back when the program finishes with it. The functions malloc() and free() provide this facility. The full prototype of malloc is:

void *malloc(size_t s);

This means that malloc() returns a pointer of type void. A void pointer is simply a mechanism for converting pointers from one type to another using casting. So, if the memory is required for a character array, the function would be called thus:

char *str;
size_t array_size;  /* set array_size to the required value before malloc */
str = (char *)malloc(array_size);

Remember that size_t is an implementation-defined variable type which is used for variable sizes. The C function sizeof() is useful when using malloc():

/* A portable call to claim space for 1000 ints */
/* The size of int varies from machine to machine */
int *numbers;
numbers=(int *)malloc(1000 * sizeof(int));

If malloc() cannot claim sufficient space from the operating system it will return a null pointer.

The structures discussed earlier in this chapter are ideally suited to the use of malloc(). A typical structure representing a block of text in a word processor might look like this:

struct block 
   struct *block prev;
   struct *block next;
   };
struct *block bptr;

To claim more memory as extra lines are added:

bptr->next = (struct block *)malloc(sizeof(struct block));
/* link the new block to the chain */
bptr->next->prev = bptr;
bptr=bprt->next;
bptr->next = NULL;  /* the end of the chain */

Once the size of the block is established, malloc() can be used to allocate the text area:

size_t block_size;

bptr->text = (char *)malloc(block_size);

To release a block of memory, the function free() is used:

free(bptr->text);

/* unlink from the chain */ 
bptr->next->prev = bptr->next; 
bptr->prev->next = bptr->prev; free(bptr);

If a block needs to be enlarged, the function realloc() will attempt to provide extra space. This may involve a malloc() for the new size followed by a copy, so the value of the pointer may change:

new_block = (char *)realloc(bptr->text, new_size);
if(new_block != NULL)
   bptr->text = new_block;

One limitation of these standard functions is that the spare memory can become fragmented, and while there may be sufficient memory there may not be a large enough contiguous chunk to service a request.

Furthermore, Acorn's implementation provides memory from the amount allocated in the program's original Wimpslot. Once this is used up, malloc() will return null pointers (implying no more memory), no matter how much there may actually be.

Bit variables

If you use flags extensively in your programs and you're coming from an assembler background, you might not like the idea of using a whole integer (4 bytes) to store a simple yes-no value. If your programs are huge, you might not have room! In a well designed language it should be possible to store a flag in a single bit, and this is true in C.

One approach is to use #define to set up bit-values. Returning to the *COPY example, options could be defined as powers of 2, corresponding to a bit:

#define FORCE_OVERWRITE 1
#define DELETE_SOURCE 2
#define VERBOSE 4
         /* ... and so on  */
int options;

To set a flag, write:

options |= VERBOSE;     /*  '|' is the bitwise OR */

To unset it, use:

options &= ~VERBOSE;    /*  '&' is the bitwise AND */

This uses a new operator: the tilde (~) which means NOT in bitwise operations. Note also the use of the <op>= shorthand.

To test such a flag, you would write:

if((options & VERBOSE) == 0)
         /* i.e. if VERBOSE is off */

However, C allows you to define a structure containing bit-fields:

struct 
   unsigned int delete_source : 1;
   unsigned int verbose : 1;
   } copy_options;

The width of the field in bits is specified by the number after the colon. In this example, copy_options contains three one-bit fields which you can access in the same way as any other structure member. For example:

copy_options.verbose = 1;
if(copy_options.delete_source == 0)

Using SWIs

A feature of RISC OS is the well documented interface to operating system routines. In Basic this is achieved with the SYS command, in assembler, SWI. Of course, C also provides a way of accessing them. Naturally enough, what follows is specific to RISC OS machines.

A header file, kernel.h, contains function prototypes, #defines and other relevant data for making SWI calls, but there are possibly better alternatives in other libraries, such as DeskLib (a freeware WIMP library).

Information is passed to and from SWI routines in the registers 0-9. These are defined as a structure _kernel_swi_regs in the kernel.h file. In your program you should include the following lines:

#include <kernel.h>
_kernel_swi_regs my_regs;

Individual registers are treated as ints and accessed as in this example:

myregs.r[4] = 1099;

Registers are often used to hold the addresses of (or pointers to) character strings or other blocks of data. C is well-suited to this requirement. The address of any variable is given by the operator &, or in the case of arrays by the unsubscripted array name:

char message[50];
struct line_data edit_line;
my_regs.r[0] = (int)message;
my_regs.r[0] = (int)&edit_line;

Pointers are cast as integers, which is the type of the register array elements. You can even pass the address of a 'character constant' or 'string literal':

my_regs.r[0] = (int) "This is a literal";

To actually call a SWI, use the routine _kernel_swi:

_kernel_swi(int swi_number, _kernel_swi_regs *in, _kernel_swi_regs *out)

The SWI numbers are #defined in swis.h so you can refer to them by their names, for example:

#include <swis.h>
_kernel_swi(os_ReadVarVal, &my_regs, &my_regs);

The following short program uses SWI calls to display any portion of the time/date display.

/* Date.c - print any portion of Time message in any specified */
/* format (see PRM). Example: date "%WE, %MO the %DY%ST"       */
/* would print "Tuesday, December the 25th"                    */
/* Written by Dec McSweeney                                    */
#include <stdio.h>
#include <stdlib.h>
#include <kernel.h>
#include <swis.h>
int main(int argc, char *argv[])

char outstring[50];
int n;
_kernel_swi_regs r;
/** First a call to OS_Word 14, 3 - read clock as 5-byte value  **/
/** r0 = 14, r1 = address of parameter block (to hold result)   **/
/** on entry, 1st byte of r1 is set to 3 (the "reason code")    **/
 
r.r[0]=14;
r.r[1]=(int)pblock;
pblock[0] = 3;
_kernel_swi(OS_Word, &r, &r);
/** on exit, pblock holds the 5-byte time - compare with a Basic **/
/** command "SYS 07, 14, pblock TO , pblock"                     **/
/** first we show the contents of pblock....                     **/
for(n=0; n < 5; n++)  printf("%02X ", pblock[n]); printf("\n");
/** Now, we call SWI &C1 to convert. argv[1] is the char array   **/
/** holding the format string. For this call the 5-byte block is **/
/** addressed via r0, the output string via r1, the format       **/
/** string via r3. The length of output array is in r2.          **/
r.r[0]=r.r[1];
r.r[1]=(int)outstring;
r.r[2]=50;
r.r[3]=(int)argv[1]; _kernel_swi(OS_ConvertDateAndTime, &r, &r);
printf("%s\n", outstring);
exit(0);
}

As it stands, the program will fall over if there is no format string in argv[1], and does no validation on the format, so it relies on OS_ConvertDateAndTime to cope with any rubbish the user might type. To test the program, run it from the command line as follows:

*date "%WE, %MO the %DY%ST"

Now you know how to do it, why not change date.c to report an error if no argument is present, or even to use a default string - you could use OS_ReadVarVal to access SYS$DateFormat, or call OS_ConvertStandardDateAndTime (SYS &60) which uses SYS$DateFormat instead of r3. A really user-friendly version would print helpful information if argv[1] was missing, invalid or set to ?.

Programming in C++ is largely similar to programming in the C language itself. On occasion, you'll see helpful, big, tutorial books available in WH Smiths; promising to guide you through C++ in considerably more detail than you need for your Acorn computer. Recognising this, we've prepared a first steps tutorial, and recommend that you refer to the Dummies series of books for any further information.

We're only covering the initial elements of a C++ program, though: and using the examples given within the C++ tutorial on the disc.

Getting output onto the screen

Within C++, the vast majority of the output communication is handled by the iostream.h library. This contains routines to handle the input into the system, and output from the system to screen, printer or modem.

Our example, in this case, repeats the output of John Doe three times, counting through the index value until index is equal to three.

#include "iostream.h"

main() cout << "John Doe 1 Jan 1955\n\n"; } }

Not very difficult to follow, really. Things can get considerably more complex once you introduce a number of libraries at the same time. That, however, is explained in considerable detail in our C++ tutorial on the program CD-ROM.

This book is currently in its third edition: we're looking for a Wimp C++ tutorial for our fourth edition, and welcome any input from purchase of Easy C++. Should you wish to become involved, please contact us via the address given on page 2.

We are planning a book on C programming, with a particular emphasis on cross platform coding (e.g. Acorn to PC to Mac). If you'd like to enquire as to availability, please contact APDL again at the address given on page 2. The expected price is £25.99.

All C programs linked to the library STDClib have access to the full range of standard ANSI functions. EasyC is supplied with all 15 header files which declare these ANSI functions. The most useful of these are declared in the 5 header files listed below, and are described in detail in this chapter:

  ctype.h
  stdlib.h
  math.h
  string.h
  stdio.h

Many more ANSI functions are declared in the 10 header files listed below. All of these may be used from EasyC, but due to their more advanced nature they are not documented in this user guide. However, the syntax of these functions can be found by studying the header files.

  assert.h
  setjmp.h
  errno.h
  signal.h
  float.h
  stdarg.h
  limits.h
  stddef.h
  locale.h
  time.h

Further information about ANSI functions can be found in the Tutorial in the previous chapter, and in most good books on C programming. For the definitive text on the C programming language and descriptions of all ANSI functions you should refer to the IOC/IEC international standard called Programming languages - C (ref. ISO/IEC 9899).

APDL and ProAction

 Index