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