This
is a real, live coding standard for Planetweb, a company that does embedded systems
- different language, different concerns, different standards.
Recommended
C Style and Coding Standards (7.23.01)
This document is based on an updated version of the Indian
Hill C Style and Coding Standards paper. It describes recommended coding
practices for C programming here at Our Company. All new code written should
conform to these rules. Of course, standards cannot cover all situations.
Experience and professional judgment count for much. Nonetheless, these
guidelines should be followed unless there is compelling reason to violate
them.
Many of the style choices here are somewhat arbitrary. That
some will chafe is understandable. However, after coding to them for a while,
they will come to seem comfortable and natural to you. The goal of these
standards is to increase readability, ensure platform independence, improve
clarity and, above all, to simplify maintenance. To meet these goals it is
essential that all programmers conform to the standard.
In the event that you are performing maintenance, please
bear in mind that mixed coding style is harder to maintain than bad coding
style. When changing existing code it is better to conform to the style
(indentation, bracing, etc) of the existing code that it is to blindly follow
this document. Unless, of course, you are making wholesale changes – this
represents an excellent opportunity to bring old code up to date.
File Naming Conventions
- Filenames
may be of any length. Meaningfulness and uniqueness of filenames is
paramount. Filenames should be all lowercase. (Some platforms have case-sensitive file systems, and some
do not. To keep things from
getting too confusing, we limit the case of filenames.)
- Try
to maintain or institute a common naming convention for files with a
related purpose (e.g. “js” for javascript). See note below about header
files, however.
- The
following suffixes are expected:
- C
source file names must end in .c
- Assembler
source file names must end in .s
- Includable
header file names end in .h
- All
header files must start with “pw” or “px”. This is to avoid conflicts with header files supplied by the
compiler environment. (For
example, “dhcp.h” is a bad name for the header file of our dhcp
implementation, since there may be a dhcp.h that’s part of the
environment.)
Program Files
The suggested order of sections for a program file is as
follows:
- File identification. The name of the file and the CVS
keyword $Header$.
- Copyright
information. Copy this boilerplate from another file and make the
appropriate substitutions for year, etc. Suggested minimum: Copyright 2001 Our Company, Inc. All Rights Reserved.
- A
prologue that tells what is in the file. A description of the purpose of
the objects in the file (whether they be functions, external data
declarations or definitions, or something else) is more useful than a list
of the object names. The prologue may optionally contain author(s),
references, etc.
- Any
header file includes should be next. If the include is for a non-obvious
reason, the reason should be commented. In most cases, standard C library
include files should be first, followed by PW system include files.
- Any
defines and typedefs that apply to the file as a whole are next.
- Next
come the global (external) data declarations, usually in the order:
non-static globals, static globals. If a set of defines applies to a
particular piece of global data (such as a flags word), the defines should
be immediately after the data declaration or embedded in structure
declarations, indented to put the defines one level deeper than the first
keyword of the declaration to which they apply.
- The
functions come next, and should be in some sort of meaningful order. Like
functions should appear together. Considerable judgment is called for
here. If defining large numbers of essentially independent utility
functions, consider alphabetical order.
- The
CVS revision log should be the last item in the file. If you don’t know
how to do this, look at an existing file.
Header Files
- Don’t
use absolute pathnames for header files. The “include-path” option of the
C compiler is the best way to handle extensive private libraries of header
files; it permits reorganizing the directory structure without having to
alter source files.
- Header
files that declare functions or external variables should be included in
the file that defines the function or variable. That way, the compiler can
do type checking and the external declaration will always agree with the
definition.
- Defining
variables in a header file is often a poor idea. (But declaring them there
is fine.)
- If
the declarations in a header file require some other header in order to
compile properly, those include directive should appear in the header file
itself. You may wish to use the
“….types.h” header files to pull in the proper types to avoid forcing
include file loops.
- It is
common to put the following into each .H file to prevent accidental
double-inclusion:
#ifndef PW_EXAMPLE_H
#define PW_EXAMPLE_H
…body of example.h file
#endif //
PW_EXAMPLE_H
Comments
The comments should describe what is happening, how
it is being done, what parameters mean, which globals are used and which are
modified, and any restrictions or bugs. Avoid, however, comments that are clear
from the code, as such information rapidly gets out of date. Comments that
disagree with the code are of negative value. Short comments should be what comments, such as “compute mean
value”, rather than how comments such
as “sum of values divided by n”.
- C is
not assembler; putting a comment at the top of a 3 to 10 line section
telling what it does overall is often more useful than a comment on each
line describing micrologic.
·
Comments should justify offensive code. The
justification should be that something bad will happen if inoffensive code is
used. Just making code faster is not enough to rationalize a hack; the
performance must be shown to be
unacceptable without the hack. The comment should explain the unacceptable
behavior and describe why the hack is a “good” fix.
- Block
comments inside a function are appropriate, and they should be tabbed over
to the same tab setting as the code that they describe.
- Use
vertical and horizontal whitespace generously. Indentation and spacing
should reflect the block structure of the code; e.g., there should be at
least 2 blank lines between the end of one function and the comments for
the next.
- Explicitly
comment variables that are changed out of the normal control flow, or
other code that is likely to break during maintenance.
- If,
during maintenance, you find a comment which is incorrect, fix it.
Declarations
- Global
declarations should begin in column 1.
- All
external data declarations should be preceded by the extern keyword, and placed in a header file. If an external
variable is an array that is defined with an explicit size, then the array
bounds must be repeated in the extern declaration unless the size is
always encoded in the array (e.g., a read-only character array that is
always null-terminated). Repeated size declarations are particularly
beneficial to someone picking up code written by another.
- All
common code external variables and functions should be prefaced with pw_ or PW_
- All
project local external variables and functions should be prefaced with px_ or PX_
- The
“pointer'' qualifier, *, should
be with the variable name rather than with the type. char *s, *t, u; instead of char
* s,t,u; which is wrong, since t and u do not get declared as
pointers.
- Unrelated
declarations, even of the same type, should be on separate lines. A
comment describing the role of the object being declared should be
included, with the exception that a list of #defined
constants do not need comments if the constant names are sufficient
documentation. For structure and union template declarations, each element
should be alone on a line with a comment describing it.
- Initialization: Any variable with global or file scope
which has no initializer will be set to zero by the C-startup code. (It is part of the porting team’s
mandate to make sure this is true. These variables often live in a section
called “.bss”.)
- Any
variable whose initial (non-zero) value is important should be explicitly initialized.
- In any
file maximum use should be made of the PW_STATIC keyword to make functions and variables local to
single files. Variables in particular should be accessible from other
files only when there is a clear need that cannot be filled in another
way. Such usage should be commented to make it clear that another file's
variables are being used; the comment should name the other file. (Static
functions and variables need not begin with PW or PX.)
- Avoid
names that differ only in case, like foo
and Foo. Similarly, avoid foobar and foo_bar. The potential for confusion is considerable.
- Pre-fixing
is not required. However, if a pre-fixing convention is used, it must
incorporate the following conventions:
- g =
global
- s =
static
- k =
constant
- p =
pointer
- m =
member
Function Declarations
- Each
function should be preceded by a block comment prologue that gives a short
description of what the function does and (if not clear) how to use it.
Discussion of non-trivial design decisions and side-effects is also
appropriate. Avoid duplicating information clear from the code.
- The
return type of functions should always be declared. The function return
type should be in column 1. Do not default to int; if the function does not return a value then it should be
given return type void. If the
value returned requires a long explanation, it should be given in the
prologue. The opening brace of the function body should be alone on a line
beginning in column 1.
- Each
parameter should be declared (do not default to int). In general the role of each variable in the function
should be described. This may either be done in the function comment or,
if each declaration is on its own line, in a comment on that line. Loop
counters called ``i'', string pointers called ``s'', and integral types
called ``c'' and used for characters are typically excluded. If a group of
functions all have a like parameter or local variable, it helps to call
the repeated variable by the same name in all functions. (Conversely,
avoid using the same name for different purposes in related functions.)
Like parameters should also appear in the same place in the various
argument lists.
- If the
function uses any external variables (or functions) that are not declared
globally in the file, then they should have been declared in a header file. Do not use the extern keyword in a C file; Use the H file which defines its
“api”.
- Wherever
possible pass pointers to structures, not structures. In addition to being
substantially more efficient, different compilers use different
conventions for returning structures. This causes a problem when libraries
return structure values to code compiled with a different compiler.
Structure pointers are not a problem.
- Do not
make assumptions about the parameter passing mechanism. especially pointer
sizes and parameter evaluation order, size, etc. The stack may grow up or
down (indeed, there need not even be a stack!). Parameters may be widened
when they are passed, so a char might be passed as an int, for instance.
Arguments may be pushed left-to-right, right-to-left, in arbitrary order,
or passed in registers (not pushed at all). The order of evaluation may
differ from the order in which they are pushed. One compiler may use
several (incompatible) calling conventions.
- Wherever
the type is in doubt, parameters should be cast to the appropriate type;
But it is always better to use the same type instead. Avoid casting const pointers to non-const pointers - that const was there for a reason. Cast void pointers to the appropriate type before use (especially
the return value of malloc). Do
not use function calls as a place to do type cheating. For example, if a
function expects a pointer to a 32-bit long and it is passed a pointer to
a 16-bit word, record structures can get corrupted.
Simple Statements
- There
should be only one statement per line unless the statements are very
closely related.
- Write
clearly. Don’t be too clever.
- Avoid
the use of embedded assignment statements (e.g. d = (a = b + c);). Even though the latter may save one cycle.
In the long run the time difference will decrease as the optimizer gains
maturity, while the difference in ease of maintenance will increase as
the human memory of what's going on in the code begins to fade.
- Accidental
omission of the second = of the logical compare is a problem. Use
explicit tests. Avoid assignment with implicit test. When embedded
assignment is used, make the
test explicit so that it doesn’t get “fixed” later (e.g. if ((abool = bbool) != FALSE)).
And, of course, comment it.
- Don't
use floating-point variables where discrete values are needed. Using a
float for a loop counter is a great way to shoot yourself in the foot.
Always attempt to test floating-point numbers as <= or >=,
instead of using an exact comparison (== or !=). (You can get away with the equality comparisons when you
have a deep understanding of floating point numbers, but if you really
needed equality operations, then you probably should have been using
integers anyway.)
Compound Statements
A compound statement is a list of statements enclosed by braces.
There are many common ways of formatting the braces. Be consistent with your
local standard, if you have one, or pick one and use it consistently. When
editing someone else's code, always
use the style used in that code.
- Braces
are essential in if-if-else sequences with no
second else, which may be
parsed incorrectly if the braces are omitted.
- If
your editor has an auto-indentation feature, USE IT.
- If you
make a compound statement in a macro, use the following boilerplate:
#define FOO(arg) do { bar(arg); baz(arg); } while (0)
Note the missing
semicolon: This forces a semicolon to
be inserted by the user of the macro, and makes the macro safe for an
unbracketed if statement. Forcing
semicolons to be used also ensures proper behavior by auto-indenters.
- The
following code is very dangerous:
#ifdef FOOBAR
#define FOO(arg) { bar(arg); }
#else
#define FOO(arg)
#endif
if (expr)
statement;
else
FOO(x)
++x;
Note that on systems where FOOBAR
is not defined the statement ++x; will only get executed when expr is false. If the macro had been
written with the do {} while (0)
style (and the semicolon added), this code would be correct.
- Sometimes
an if causes an unconditional
control transfer via break, continue, goto, or return.
The else should be implicit and
the code should not be indented.
Anti-Bugging
- Numerical
constants should not be coded directly. The #define feature of the C preprocessor should be used to give
constants meaningful names. Symbolic constants make the code easier to
read. Defining the value in one place also makes it easier to administer
large programs since the constant value can be changed uniformly by
changing only the define. The enumeration data type is a better way to
declare variables that take on only a discrete set of values, since
additional type checking is often available (and debuggers may understand
them). At the very least, any directly-coded numerical constant must have
a comment explaining the derivation of the value.
- If
you use enums, the enum constant with value zero should
be the default or uninitialized value.
This allows the “bss wipe” to set these variables properly.
- Complex
expressions can be used as macro parameters, and operator-precedence
problems can arise unless all occurrences of parameters have parentheses
around them. There is little that can be done about the problems caused by
side effects in parameters except to avoid side effects in expressions (a
good idea anyway) and, when possible, to write macros that evaluate their
parameters exactly once. There are times when it is impossible to write
macros that act exactly like functions.
- Macros
should avoid using globals, since the global name may be hidden by a local
declaration. Macros that change named parameters (rather than the storage
they point at) or may be used as the left-hand side of an assignment
should mention this in their comments. Macros that take no parameters but
reference variables, are long, or are aliases for function calls should be
given an empty parameter list.
- Don't
change syntax via macro substitution. It makes the program unintelligible
to all but the perpetrator.
- Write
portable code first, worry about detail optimizations only on machines
where they prove necessary. Optimized code is often obscure. Optimizations
for one machine may produce worse code on another. Document performance
hacks and localize them as much as possible.
- Please
be aware of declared variable sizes and their uses, especially when
passing
pointers around. I just tracked down a compiler warning where a
pointer
to an "Sint" was being passed as an argument to a routine that was
expecting
a "Sint32 *". While on systems where an Sint gets compiled as a
32 bit
value this won't cause a problem, that is NOT true of all systems. If
you get compiler warnings because of mis-matched arguments,
PLEASE fix them before checking the code in!
- Most platforms use Uint8 for PW_CHAR. Some use
Sint8 for PW_CHAR. Please be careful of your usage of PW_CHAR.
- Avoid assuming ASCII.
If you must assume, document and localize. Remember that characters may
hold (much) more than 8 bits.
- The bytes of a word are of increasing
significance with increasing address on machines such as the x86 (little-endian) and of decreasing
significance with increasing address on other machines such as the 68000
(big-endian). The order of bytes in a word and of words in larger objects
(say, a double word) might not be the same. Hence any code that depends on
the left-right orientation of bits in an object 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. Actually, it
is nonportable to concatenate any
two variables.
- Word size also affects shifts and masks. The
following code will clear only the three rightmost bits of an int on some 68000s. On other machines it
will also clear the upper two bytes x
&= 0xfff8. Use instead x
&= ~07 which works properly on all machines. Bitfields do not have
these problems.
- Do NOT ignore compiler warnings: Fix them. Any
new code, changes, fixes or revisions must be warning free before you
check them in.
- Please note that the functions “min” and “max”
are not part of the standard C library. As such we have provided our own
versions for Our Company common code. They are: PW_MIN and PW_MAX. Please
use them exclusively.