[comp.lang.c] How do a write portable programs?

warner@hydrovax.nmt.edu (M. Warner Losh) (09/05/88)

write portable 'C' code.  Things like don't use 0, use NULL, but be
sure to always cast it.  Don't assume that a char is n bits long, but
you can assume that it will hold any character the machine you are on 
supports.  Side effects were also discussed.  Since all of that has been 
beat into the ground, I'd like to discuss the next level up.  Once you have 
done all the correct language bashing, what do you do about basic 
differences in environment?

How do I write program that are easily protable when I HAVE TO use the
system calls (be they setitimer() or lib$init_timer() or int21()...).  Is
there a good and easy way that I can write my programs so that most of the
code never has to be touched when I port?  What is the best way of handling
system dependent routines?  Assume the whole world is UNIX and then write
UNIX system call emulation on those that aren't?  Or should I write
routines that will do some functions (say turn off echo, but that is a bad
example) regardless of how that function gets done.  Should I use zillions
of #ifdefs everywhere? or should there be files that contain source code
for only one system? 

That should be enough questions to start a lively disccussion.  I'm looking 
for how it is done in the real world and what the realtive strengths and 
weaknesses of the various methods are (#ifdefs are easy, but hard to read.  
More files are easier to read, but harder to maintain, etc) 

Cheers,
Warner Losh
--
warner%hydrovax@nmt.edu		...!unmvax!nmtsun!warner%hydrovax
..there was a *third* possibility we hadn't counted upon ...
My spelling and my views are my own.  Only the letters have been changed...

gwyn@smoke.ARPA (Doug Gwyn ) (09/08/88)

In article <1056@nmtsun.nmt.edu> warner@hydrovax.nmt.edu (M. Warner Losh) writes:
>How do I write program that are easily protable when I HAVE TO use the
>system calls (be they setitimer() or lib$init_timer() or int21()...).  Is
>there a good and easy way that I can write my programs so that most of the
>code never has to be touched when I port?  What is the best way of handling
>system dependent routines?  Assume the whole world is UNIX and then write
>UNIX system call emulation on those that aren't?  Or should I write
>routines that will do some functions (say turn off echo, but that is a bad
>example) regardless of how that function gets done.  Should I use zillions
>of #ifdefs everywhere? or should there be files that contain source code
>for only one system? 

There is one basic answer to all these questions:  Encapsulate system
dependencies in a set of interface modules, where you have carefully
designed the interface to be as general as possible while providing
the necessary functionality to support your application(s).  For
example, even though some systems support record locking, others
don't, so if you need data integrity while updating records in a file
see if you can arrange things so that whole-file locking will work
acceptably.  Then implement a simple lock/unlock file interface that
does whatever is necessary to lock an entire file.  In a single-user
environment, these will be no-ops, in an old UNIX environment, you
can probably use the "link to the file" kludge, and a 4BSD or SVR2.2
or later environment provides direct kernel support for file locking.

The important point is that your applications proper contain
"universal" calls to the appropriate interface routines; when porting,
only the internals of the interface routines (which should be kept in
separate source files) should require revision.  This is just an
extension of the "C library" approach, and it works quite well
provided that the system interface is well designed.

It doesn't take many #ifdefs to make one's code virtually unreadable,
and one has the problem of properly parameterizing them, which is not
as easy as one might think due to the existence of a variety of
"hybrid" environments.  (For example, a "SYSV" parameter might
not discriminate sufficiently on a mostly-4BSD system with some SYSV
features added, or vice-versa).

By the way, associated with the "universal" system interfaces are
normally one or more headers that declare the functions, define
constants, etc.  You can use such a header to accommodate the lack of
a "void" type, and so forth, but don't overdo it or nobody except
possibly you will be able to read your application's source code.

daveh@marob.MASA.COM (Dave Hammond) (09/08/88)

In article <1056@nmtsun.nmt.edu> warner@hydrovax.nmt.edu (M. Warner Losh) writes:
>How do I write program that are easily protable when I HAVE TO use the
>system calls (be they setitimer() or lib$init_timer() or int21()...).  Is
>there a good and easy way that I can write my programs so that most of the
>code never has to be touched when I port?  What is the best way of handling
>system dependent routines?  Assume the whole world is UNIX and then write
>UNIX system call emulation on those that aren't?  Or should I write
>routines that will do some functions (say turn off echo, but that is a bad
>example) regardless of how that function gets done.  Should I use zillions
>of #ifdefs everywhere? or should there be files that contain source code
>for only one system? 

I suggest separating the code into application-level (code which should never
be concerned with the processor its running on) and system-level (device
i/o, subprocess control, signal handling, etc) routines.  Then develop a
standard interface between the 2 levels.  In this way you should only have to
heavily #ifdef the system-level routines.  For example, if echo is the
question, your application-level code might call a function Set_Echo_Off()
[a system-level routine] which should contain the #ifdefs relevant to echo
handling on various systems.

Dave Hammond
  UUCP: uunet!masa.com!{marob|dsix2}!daveh
DOMAIN: daveh@marob.masa.com
------------------------------------------------------------------------------

jones@ingr.UUCP (Mark Jones) (09/08/88)

In article <1056@nmtsun.nmt.edu>, warner@hydrovax.nmt.edu (M. Warner Losh) writes:
> write portable 'C' code.  Things like don't use 0, use NULL, but be
> sure to always cast it.

Instead of using NULL, use ZERO, and don't worry about casting it.  In K
& R (first edition) page 192 the following statement is made regarding
assignment operators. "... it is guaranteed that assignment of the
constant 0 to a pointer will produce a null pointer distinguishable from
a pointer to any object." (For a very close look at this subject, get
sept/oct issue of "The C Users Journal" (published by CUG) pages 29-36.

> How do I write program that are easily protable when I HAVE TO use the
> system calls (be they setitimer() or lib$init_timer() or int21()...).  Is
> there a good and easy way that I can write my programs so that most of the
> code never has to be touched when I port?  What is the best way of handling
> system dependent routines?  Assume the whole world is UNIX and then write
> UNIX system call emulation on those that aren't?  Or should I write
> routines that will do some functions (say turn off echo, but that is a bad
> example) regardless of how that function gets done.  Should I use zillions
> of #ifdefs everywhere? or should there be files that contain source code
> for only one system? 
> 

If at all possible, stick to std K & R functions (if they are not in the
K & R book (first edition), don't use them.  If that isn't possible, try
to find a function in the ANSI C spec that will do what you want.  If
there still isn't one, write your own, and in that function, place your
#ifdefs to handle different OS, here is where you would place
lib$init_timer() and int21 calls.  Keep the main body of your code clear
of anything system dependent.

Do not rely on the size of anything.  If there is a need for a 32-bit
number, do it like this:

#if sizeof(int) == 4
typedef bit32 int			/* pick your own name here */
#else
#if sizeof(long) == 4
typedef bit32 long
#endif
#endif

Watch out for sign extension, If you plan to assign a character to an
int, declare it an unsigned if you want it to still be a character when
it gets there.  For example:
{
char c = 0x85;
int  i;				/* assuming 32 bit ints */
	i = c;		/* i = 0xffffff85 */
}
or
{
unsigned char c = 0x85
int i;
	i = c;		/* i = 0x00000085 */
}

For the next few years, try to stay with K & R, and don't use ANSI
extensions, this is because not everyone has the ANSI extensions.  For
examples, if you use prototypes (and you should), place them in a header
file and put #ifdefs around them.


> More files are easier to read, but harder to maintain, etc) 

Try to keep things broken into several files, based on functionality,
the small amount of overhead in maintaining the program is worth it when
it comes to getting it running on a foreign target, also, if the
communications facilities for porting the source are poor, it can save
some real headaches.

Mark Jones

wietse@wzv.UUCP (Wietse Venema) (09/09/88)

In article <1056@nmtsun.nmt.edu> warner@hydrovax.nmt.edu (M. Warner Losh) writes:
>How do I write program that are easily protable when I HAVE TO use the
>system calls (be they setitimer() or lib$init_timer() or int21()...).  Is
>there a good and easy way that I can write my programs so that most of the
>code never has to be touched when I port?  What is the best way of handling
>system dependent routines?  Assume the whole world is UNIX and then write
>UNIX system call emulation on those that aren't?  Or should I write
>routines that will do some functions (say turn off echo, but that is a bad
>example) regardless of how that function gets done.  Should I use zillions
>of #ifdefs everywhere? or should there be files that contain source code
>for only one system? 

You might take a look at `software tools' by Kernighan and Plauger.
It's a little old (1978) and uses a different language (Ratfor), but
provides good examples of encapsulating system-dependent code in a small
collection of interface routines.  I must admit that this portable
interface is patterned after the standard UNIX programming interface.

These ideas have been elaborated by some people (at Berkeley?) and
resulted in VOS, a virtual operating system. Basically, it is a thin
layer of routines that map requests to system calls of the local OS.
Porting software then becomes a matter of writing an appropriate VOS
implementation.

			Wietse
-- 
work:	wswietse@eutrc3.uucp	| Eindhoven University of Technology
ditto:	wswietse@heitue5.bitnet	| Mathematics and Computing Science
home:	wietse@wzv.uucp		| 5600 MB Eindhoven, The Netherlands

ok@quintus.uucp (Richard A. O'Keefe) (09/09/88)

In article <2515@ingr.UUCP> jones@ingr.UUCP (Mark Jones) writes:
>Instead of using NULL, use ZERO, and don't worry about casting it.

This is a joke, right?  Yes, it works for assignment to variables,
but it is _not_ good for passing to functions without prototypes!

>Do not rely on the size of anything.  If there is a need for a 32-bit
>number, do it like this:
>
>#if sizeof(int) == 4

Now I *know* it's a joke!  "sizeof" is not legal in #if.  

karl@haddock.ima.isc.com (Karl Heuer) (09/10/88)

In article <2515@ingr.UUCP> jones@ingr.UUCP (Mark Jones) writes:
>In article <1056@nmtsun.nmt.edu>, warner@hydrovax.nmt.edu (M. Warner Losh) writes:
>> write portable 'C' code.  Things like don't use 0, use NULL, but be
>> sure to always cast it.
>
>Instead of using NULL, use ZERO, and don't worry about casting it.  [Because
>K&R guarantees that `0' denotes a null pointer constant.]

`NULL' is more meaningful than `0', in a context where it isn't otherwise
obvious that you're dealing with a pointer.  But since we're talking about
portability issues, I'll grant that they're equally good ways to represent a
null pointer constant.  But the cast is required in one situation: a function
argument not covered by a prototype.

>#if sizeof(int) == 4

You can't use sizeof() in an #if expression.  If you have ANSI C's <limits.h>,
you can write as `#if INT_MAX >= 0x7fffffff' to get the effect you want.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

g-rh@cca.CCA.COM (Richard Harter) (09/10/88)

In article <1056@nmtsun.nmt.edu> warner@hydrovax.nmt.edu (M. Warner Losh) writes:
>
>How do I write program that are easily protable when I HAVE TO use the
>system calls (be they setitimer() or lib$init_timer() or int21()...).  Is
>there a good and easy way that I can write my programs so that most of the
>code never has to be touched when I port?

Here are some practical suggestions which will simplify life for you if you
are writing portable code.  The most important thing is to isolate and segregate
the calls to system routines.  For example, suppose you want the date.  Now it
happens that the format of the date string returned from ctime is different
in different operating systems (Primos returns a 24 char string in the standard
Primos date format).  If you have calls to ctime scattered in your code you
will get an unpleasant little surprise the first time you try to port to Primos.[Experience speaks.]  If, however, you have your own date routine which calls
the system routines you only have one kludge to fix.  The rule is general --
wherever feasible go through an interface routine and only make the system
call once.  If this is not desirable attempt to isolate the system calls to
a handful of routines.

A second device is to create your own types for system arguments via define
statements in an include file which contains machine dependencies.  This 
protects you against various differences in argument types.

A particular case is the code for opening files (open and fopen both).
You will find that the arguments for these routines should be different
in PRIMOS and in VMS than they are in UNIX.  For example, suppose that you
want to open a stream file in append mode.  Here are the three statements
that you want:

UNIX	ptr = fopen(filename,"a");
VMS	ptr = fopen(filename,"a","rfm=var","rat=cr");
PRIMOS	ptr = fopen(filename,"wa");

Now you clearly do better if you have stashed away a define that looks

#ifdef VMS
#define APPEND_MODE "a","rfm=var","rat=cr"
#endif
#ifdef PRIMOS
#define APPEND_MODE "wa"
#endif
#ifdef UNIX
#define APPEND_MODE "a"
#endif

and open your stream files in append mode with the statement

	ptr = fopen(filename,APPEND_MODE);

-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

danw@tekchips.CRL.TEK.COM (Daniel E. Wilson;1432;58-790;;) (09/10/88)

In article <2515@ingr.UUCP> jones@ingr.UUCP (Mark Jones) writes:
>In article <1056@nmtsun.nmt.edu>, warner@hydrovax.nmt.edu (M. Warner Losh) writes:
>Instead of using NULL, use ZERO, and don't worry about casting it.  [Because
>K&R guarantees that `0' denotes a null pointer constant.]
 
    I for one have problems with this.  There is an Intel C compiler that
will not handle 0 for a NULL pointer in the large memory model.  This is
due to the lack of argument checking in K&R C.  (C++ is much better.)

If you wish for a program to be portable do this:

	#define    NIL(type)  ((type *) 0)

Always use this function and you avoid the problem.
--------------
Dan Wilson <shadow@tekfdi.FDI>

These opinions are my own.
My manager would prefer that I didn't have opinions.

tps@chem.ucsd.edu (Tom Stockfisch) (09/11/88)

In article <2515@ingr.UUCP> jones@ingr.UUCP (Mark Jones) writes:

>In article <1056@nmtsun.nmt.edu>, warner@hydrovax.nmt.edu (M. Warner Losh) writes:

>> How do I write program that are easily protable when I HAVE TO use the
>> system calls ...

>If at all possible, stick to std K & R functions (if they are not in the
>K & R book (first edition), don't use them.

So I shouldn't use:
	
	malloc()
	setbuf()
	fflush()
	feof() ?
-- 

|| Tom Stockfisch, UCSD Chemistry	tps@chem.ucsd.edu

bill@proxftl.UUCP (T. William Wells) (09/13/88)

In article <404@marob.MASA.COM> daveh@marob.masa.com (Dave Hammond) writes:
: I suggest separating the code into application-level (code which should never
: be concerned with the processor its running on) and system-level (device
: i/o, subprocess control, signal handling, etc) routines.  Then develop a
: standard interface between the 2 levels.  In this way you should only have to
: heavily #ifdef the system-level routines.  For example, if echo is the
: question, your application-level code might call a function Set_Echo_Off()
: [a system-level routine] which should contain the #ifdefs relevant to echo
: handling on various systems.

Better yet, unless the system specific files are very similar, create
separate files for each system.

---
Bill
novavax!proxftl!bill