Modern C compilers support some or all of the ANSI proposed standard C. Whenever possible, write code to run under standard C, and use features such as function prototypes, constant storage, and volatile storage. Standard C improves program performance by giving better information to optimizers. Standard C improves portability by insuring that all compilers accept the same input language and by providing mechanisms that try to hide machine dependencies or emit warnings about code that may be machine-dependent.
Write code that is easy to port to older compilers. For instance, conditionally #define new (standard) keywords such as const and volatile in a global .h file. Standard compilers pre-define the preprocessor symbol __STDC__ (8). The void* type is hard to get right simply, since some older compilers understand void but not void*. It is easiest to create a new (machine- and compiler-dependent) VOIDP type, usually char* on older compilers.
#if __STDC__
typedef void *voidp;
# define COMPILER_SELECTED
#endif
#ifdef A_TARGET
# define const
# define volatile
# define void int
typedef char *voidp;
# define COMPILER_SELECTED
#endif
#ifdef ...
...
#endif
#ifdef COMPILER_SELECTED
# undef COMPILER_SELECTED
#else
{ NO TARGET SELECTED! }
#endif
Note that under ANSI C, the '#' for a preprocessor directive must be the first non-whitespace character on a line. Under older compilers it must be the first character on the line.
When a static function has a forward declaration, the forward declaration must include the storage class. For older compilers, the class must be "extern". For ANSI compilers, the class must be "static". but global functions must still be declared as "extern". Thus, forward declarations of static functions should use a #define such as FWD_STATIC that is #ifdeffed as appropriate.
An "#ifdef NAME" should end with either "#endif" or "#endif /* NAME */" not with "#endif NAME". The comment should not be used on short #ifdefs, as it is clear from the code.
ANSI trigraphs may cause programs with strings containing "??" may break mysteriously.
The style for ANSI C is the same as for regular C, with two notable exceptions: storage qualifiers and parameter lists.
Because const and volatile have strange binding rules, each const or volatile object should have a separate declaration.
int const *s; /* YES */ int const *s, *t; /* NO */
Prototyped functions merge parameter declaration and definition in to one list. Parameters should be commented in the function comment.
/*
* 'bp': boat trying to get in.
* 'stall': a list of stalls, never NULL.
* returns stall number, 0 => no room.
*/
int
enter_pier (boat_t const *bp, stall_t *stall)
{
...
Function prototypes should be used to make code more robust and to make it run faster. Unfortunately, the prototyped declaration
extern void bork (char c);is incompatible with the definition
void bork (c) char c; ...The prototype says that c is to be passed as the most natural type for the machine, possibly a byte. The non-prototyped (backwards-compatible) definition implies that c is always passed as an int (9). If a function has promotable parameters then the caller and callee must be compiled identically. Either both must use function prototypes or neither can use prototypes. The problem can be avoided if parameters are promoted when the program is designed. For example, bork can be defined to take an int parameter.
The above declaration works if the definition is prototyped.
void
bork (char c)
{
...
Unfortunately,
the prototyped syntax will cause non-ANSI compilers to reject the
program.
It is easy to write external declarations that work with both prototyping and with older compilers (10).
#if __STDC__ # define PROTO(x) x #else # define PROTO(x) () #endif extern char **ncopies PROTO((char *s, short times));Note that PROTO must be used with double parentheses.
In the end, it may be best to write in only one style (e.g., with prototypes). When a non-prototyped version is needed, it is generated using an automatic conversion tool.
Pragmas are used to introduce machine-dependent code in a controlled way. Obviously, pragmas should be treated as machine dependencies. Unfortunately, the syntax of ANSI pragmas makes it impossible to isolate them in machine-dependent headers.
Pragmas are of two classes. Optimizations may safely be ignored. Pragmas that change the system behavior ("required pragmas") may not. Required pragmas should be #ifdeffed so that compilation will abort if no pragma is selected.
Two compilers may use a given pragma in two very different ways. For instance, one compiler may use "haggis" to signal an optimization. Another might use it to indicate that a given statement, if reached, should terminate the program. Thus, when pragmas are used, they must always be enclosed in machine-dependent #ifdefs. Pragmas must always be #ifdefed out for non-ANSI compilers. Be sure to indent the '#' character on the #pragma, as older preprocessors will halt on it otherwise.
#if defined(__STDC__) && defined(USE_HAGGIS_PRAGMA) #pragma (HAGGIS) #endif
"The '#pragma' command is specified in the ANSI standard to have an arbitrary implementation-defined effect. In the GNU C preprocessor, '#pragma' first attempts to run the game 'rogue'; if that fails, it tries to run the game 'hack'; if that fails, it tries to run GNU Emacs displaying the Tower of Hanoi; if that fails, it reports a fatal error. In any case, preprocessing does not continue." - Manual for the GNU C preprocessor for GNU CC 1.34.
9. Such automatic type promotion is called widening. For older compilers, the widening rules require that all char and short parameters are passed as ints and that float parameters are passed as doubles.
10. Note that using PROTO violates the rule "don't change the syntax via macro substitution." It is regrettable that there isn't a better solution.
contents
Portability
Special Considerations