C Portability

Authors

Publication

Pub Details

Date

Pages

See all articles from QL Hacker's Journal 17

This article is dedicated to show some rather randomly chosen aspects of portable programming with C68 on the QL.

A ‘C’ program is fully portable if the source text can be compiled on any operating system which provides a standard compiler (usually the standard is defined by K&R or ANSI) without any changes at all. Although the ‘C’ language is specifically designed to allow hardware and operating system independent programming, full portability is often impossible to achieve. That is because there are no generally accepted interfaces to more advanced harware (eg. graphics, sound) and some obsolete but very common operating systems (namely MS/DOS) pose unpractical limitations, such as file name layout and memory allocation. However, ‘C’ is superior in portability to other popular languanges in that from the early beginning the standard defined exact library specifications for input/output purposes including file handling, string handling, memory management, mathematics including representation of numbers, error trapping, system date handling and more.

Let’s come to the example program and go into some details:

Programs (not just those programmed in ‘C’) are often organised in modules if they become large. This speeds up production of executable code because with the help of the MAKE utility, only those modules which have been altered are recompiled. Each source module is held in a single file, and is a good idea to give each of them a small header that gives a brief description of the module, date and author of the program. Also changes could be documented in detail there.

At the top of a program or module are virtually always #include lines which tell the preprocessor to merge other files, for example header files containing prototypes for library function (thus allowing the compiler to detect the wrong use of a function). If your program should be portable then only #include those header files which are explicitely mentioned in K&R -if you are not merging your own which are of course supplied with the source. You will find that K&R uses a dot to seperate the extention from the base file name (eg. stdio.h) and that this is not the case for C68 where an underscore is used (eg. stdio_h). Nevertheless, always use the dot in your source, C68 will translate that correctly.

If you feel necessary to use system specific calls or declarations than you can either put it in a seperate file (not used here) or put the relevant source lines in an #ifdef…#endif clause; the preprocessor will then make these lines invisible to the compiler if the condition (here: ‘QDOS’ defined) if not satisfied.

Every ‘C’ program has to contain the main() function somewhere. Always declare this as integer, even if you do not intend to return anything to the program which started started yours. ANSI clearly forbids any else but integer. Recently, C68 was modified to allow ‘void’ and others as well (I forgot why but I think it had something to do with implementation of ANSI prototyping) but it is best if you ignore this.

The example uses several printf() calls to write messages. They will always mention the program name (argv[0]) because if a program is running in a multitasking environment from a shell, ie. if is runs concurrently with other programs and has to share a scrolling text window with them, it might be difficult to identify the origin of a message. In the example, this should be not necessary because it is very fast in operating (or my QL with the speedy Gold Card).

By the way, you may wonder why all printf()s are preceeded by (void). It has nothing to do with portability: printf() is an integer that returns the number of actually written characters, since I have no need for these values, I am ignoring them and the compiler will therefore produce warnings if these are not suppressed.

I personally use C68 at the highest warning level, level six. It can activated from CC with the ‘-Qwarn=6’ option, but I avoided this in the example MAKEFILE; I have set the environment variable ‘C68’ set to ‘-warn=6 -error=3 -maxerr=4’ in my BOOT file. You may find the warning annoying, but they help a lot to detect mistakes before they can cause crashes. However, the compiler of C68 v4.12, c68 v4.4 beta, issues one particuliar warning which I am ignoring with good reason: “assignment in conditional context”.

The ‘y’ variable has been introduced to seperate calculation from the printf() that cares for output of the result. On some systems the line may be not be flushed and multitasking stopped until calculation is complete, ie. until the full line can be written.

The use of DBL_EPSILON allows the compiler to adapt the program to the precision of floating point calculation by replacing DBL_EPSILON by a constant describing this precision. With very bad floating point packages (not C68!) it might happen that the while() loop in the root() sub-routine will never stop. Blame the compiler!

The if() condition at the ‘default’ label in the switch() could be replaced by a ENOERR label in the switch(), but this is not mentioned in K&R, 2nd edition, so this construction here is safer.

It is recommended that you keep lines not longer than 70 to a maximum of 80 characters, that makes it easier to send them by email without some communication software wrapping lines. Ok, that’s not important for portability.

MAKEFILEs are not portable but you can at least simplify porting if you make file name suffix flexible as in the example makefile. The -L$(LIB_DIR) corrects an inconvenience in the current release of C68, (the linker does not read the LIB_DIR environment variable) I am sure that this will be fixed one of the next C68 releases. At time of writing, version 3.05 is up-to-date.

The example program (root_c):

/*
* Demonstration of a simple algorithm to find the square
* root of real non-negative numbers, already known to
* the old Greek and Babylonian cultures.
*
* Franz Herrmann, 9/5/93
*/


#include <stdio.h> /* printf() */
#include <errno.h> /* errno, EDOM, ERANGE */
#include <float.h> /* DBL_EPSILON */
#include <stdlib.h> /* atof() */

#ifndef ABS
#define ABS(x) ((x) < 0 ? -(x) : (x))
#endif

#ifdef QDOS
char _prog_name[] = "ROOT";
#endif

/* #define VERBOSE */


double root (double x)
{
double a = 0.1;
/* estimation for square root of x */
double tmp;
/* temporary variable */
double delta = 1.0;
/* difference describing process */

while (delta > DBL_EPSILON) {
tmp = a;
a = (a + x/a) / 2.0;
delta = ABS(tmp - a);
#ifdef VERBOSE
(void) printf("delta = %5.20f\n", delta);
#endif
}

return a;
}


int main (int argc, char *argv[])
{
double x; /* argument as floating point number */
double y; /* result */

if (argc != 2) {
(void) printf("%s: Usage: root <x>\n", argv[0]);
return -1;
}

if ((x = atof(argv[1])) < 0) {
(void) printf("%s: Negative arguments not allowed.
\n", argv[0]);
return -1;
}

switch (errno) {
case EDOM: (void) printf("%s: Invalid
argument.\n", argv[0]);
return -1;

case ERANGE: if (x == 0.0)
(void) printf("%s: Range %srflow.\n",
(x == 0.0 ? "unde" : "ove") , argv[0]);
return -1;

default: if (errno) {
(void) printf("%s: Unknown error %d.\n",
argv[0], errno);
return -1;
}
break;
}

(void) printf("%s: Calculating...\n", argv[0]);

y = root(x);
(void) printf("%s: The square root of %f is %f.\n",
argv[0], x, y);

return 0;
}

And here comes the makefile:

#
# ROOT program's MAKEFILE.
#
# Should be fairly portable. Tested on Sinclair QL
# with C68 v4.12.
#
# ROOT is the name of the compiled code, SFX the suffix
# for source files.
#

ROOT = ram2_root
SFX = _c

$(ROOT): root$(SFX) makefile
cc -QO root$(SFX) -o$(ROOT) -L$(LIB_DIR) -lm

To compile the example program with C68 on the QL, copy the source text to ‘root_c’ and the makefile to ‘makefile’ where both files must be in the same directory. Now redirect the default data device (that is the current directory) to this directory, for example with DATA_USE. Finally execute MAKE, eg. type ‘ex make’ from SuperBASIC. The executable code (24k) will be placed in ‘ram2_root’ unless you modify the MAKEFILE to your needs.

‘ex root;”10″‘ (‘root 10’ from a shell) prints:

    ROOT: Calculating...
ROOT: The square root of 10.000000 is 3.162278.

Try to find the root of 1e300 to see how the example can slow down and to watch the numbers!

All comments welcome. I claim no copyright over the ROOT program.

Enjoy Coca Cola, errr… programming.

Products

 

Downloadable Media

 

Image Gallery

People

No people associated with this content.

Scroll to Top