The advantages of portable code are well known. This section gives some guidelines for writing portable code, where the definition of portable is taken to mean that a source file contains portable code if it can be compiled and executed on different machines with the only source change being the inclusion of possibly different header files. The header files will contain defines and typedefs that may vary from machine to machine. contains useful information on both style and portability. Many of the recommendations in this document originated in . The following is a list of pitfalls to be avoided and recommendations to be considered when designing portable code:
- First, one must recognize that some things are inherently non-portable. Examples are code to deal with particular hardware registers such as the program status word, and code that is designed to support a particular piece of hardware such as an assembler or I/O driver. Even in these cases there are many routines and data organizations that can be made machine independent. It is suggested that source file be organized so that the machine-independent code and the machine-dependent code are in separate files. Then if the program is to be moved to a new machine, it is a much easier task to determine what needs to be changed. It is also possible that code in the machine-independent files may have uses in other programs as well.
- Pay attention to word sizes. The following sizes apply to basic types in C for the machines that will be used most at IH:
123456type pdp11 3B IBM________________________char 8 8 8short 16 16 16int 16 32 32long 32 32 32
In general if the word size is important, short or long should be used to get 16 or 32 bit items on any of the above machines. If a simple loop counter is being used where either 16 or 32 bits will do, then use int, since it will get the most efficient (natural) unit for the current machine.
- Word size also affects shifts and masks. The code
1x &= 0177770
will clear only the three rightmost bits of an int on a PDP11. On a 3B it will also clear the entire upper halfword. Use
1x &= ~07
instead which works properly on all machines.
- Code that takes advantage of the two’s complement representation of numbers on most machines should not be used. Optimizations that replace arithmetic operations with equivalent shifting operations are particularly suspect. You should weigh the time savings with the potential for obscure and difficult bugs when your code is moved, say, from a 3B to a 1A.
- Watch out for signed characters. On the PDP-11, characters are sign extended when used in expressions, which is not the case on any other machine. In particular, getchar is an integer-valued function (or macro) since the value of EOF for the standard I/O library is -1, which is not possible for a character on the 3B or IBM.
- The PDP-11 is unique among processors on which C exists in that the bytes are numbered from right to left within a word. All other machines (3B, IBM, Interdata 8/32, Honeywell) number the bytes from left to right. Hence any code that depends on the left-right orientation of bits in a word deserves special scrutiny. Bit fields within structure members will only be portable so long as two separate fields are never concatenated and treated as a unit.
- Do not default the boolean test for non-zero, i.e.
1if (f() != FAIL)
is better than
even though FAIL may have the value 0 which is considered to mean false by C. This will help you out later when somebody decides that a failure return should be -1 instead of 0.
- Be suspicious of numeric values appearing in the code. Even simple values like 0 or 1 could be better expressed using defines like FALSE and TRUE (see previous item). Any other constants appearing in a program would be better expressed as a defined constant. This makes it easier to change and also easier to read.
- Become familiar with existing library functions and defines. You should not be writing your own string compare routine, or making your own defines for system structures. Not only does this waste your time, but it prevents your program from taking advantage of any microcode assists or other means of improving performance of system routines.
- Use lint. It is a valuable tool for finding machine-dependent constructs as well as other inconsistencies or program bugs that pass the compiler