Do not assume compiler-specific features such as the GCC
-pipe option or nested functions are available. These
will come around and bite you the second somebody ports to a
non-Linux, non-GCC system.
Code required for portability should be isolated to a single
area and a single set of source files (for example, an
os subdirectory). Compiler, library and
operating system interfaces with portability issues should be
abstracted to files in this directory.
A portability layer is a library (or perhaps just a set of
macros in header files) that abstracts away just the parts of an
operating system's API your program is interested in. Portability
layers make it easier to do new software ports. Often, no member of
the development team knows the porting platform (for example, there
are literally hundreds of different embedded operating systems, and
nobody knows any significant fraction of them). By creating a separate
portability layer, it becomes possible for a specialist who knows a
platform to port your software without having to understand anything
outside the portability layer.
Portability layers also simplify applications. Software rarely
needs the full functionality of more complex system calls such as
mmap(2)
or
stat(2),
and programmers commonly configure such complex interfaces
incorrectly. A portability layer with abstracted interfaces
(say, something named __file_exists instead of a call to
stat(2))
allows you to import only the limited, necessary functionality from
the system, simplifying the code in your application.
Portability choices can be made along either lines of code or
compiled files. It doesn't make a difference if you select alternate
lines of code on a platform, or one of a few different files. A rule
of thumb is to move portability code for different platforms into
separate files when the implementations diverge significantly (shared
memory mapping on Unix vs. Windows), and leave portability code in a
single file when the differences are minimal (for example, whether
you're using gettimeofday, clock_gettime, ftime or time to
find out the current time-of-day).
For anywhere outside a portability layer, heed this advice:
|
#ifdef and #if are last resorts, usually a sign of failure
of imagination, excessive product differentiation, gratuitous
“optimization” or accumulated trash. In the middle of
code they are anathema. /usr/include/stdio.h
from GNU is an archetypical horror.
|
|
--
Doug McIlroy
|
|
Use of #ifdef and #if is permissible (if well controlled) within a
portability layer. Outside it, try hard to confine these to
conditionalizing #includes based on feature
symbols.
Never intrude on the namespace of any other part of the system,
including filenames, error return values and function names. Where
the namespace is shared, document the portion of the namespace that you
use.
Choose a coding standard. The debate over the choice of standard
can go on forever — regardless, it is too difficult and
expensive to maintain software built using multiple coding standards,
and so some common style must be chosen. Enforce your coding standard
ruthlessly, as consistency and cleanliness of the code are of the
highest priority; the details of the coding standard itself are a
distant second.