MH-CRC IPL C Style Guide
April 3, 2001
C Style and Coding Standards
for the Iowa MC-CRC Image Processing Lab
IPL Programming Projects
Purpose
The purpose of this project is to define a uniform style for the all programming projects within the Iowa MC-CRC image processing laboratory. There is no claim that this document is unique in any way. It was based primarily on the Indian Hills C Style and Coding Guide and the SDM C Coding Manual. The generation of a well defined coding style for the IPL imaging lab should benefit all programmers by defining a style that should help to eliminate common bugs, ease the reading of code, and improve the ability to maintain the code. By defining a common style for the IPL it is believed that this will help to facilitate large development projects, ease the burden of maintenance and increase the portability of the code. The goal of this guide is to define a style that is:
1) Consistent across programmers
2) Easy to read and understand upon inspection
3) Free of common error types
4) Maintainable by various programmers in the lab
3) Portable to various architectures
This guide covers code generation from file
organization and naming conventions to the style of statements and
operators. While this guide was initially developed for the BRAINS2
programming project, all IPL projects and programs should conform to
the defined style. This should help facilitate the maintenance of all
IPL programs.
File Organization
A file consists of several sections that should be
separated by several blank lines. Although there is not set maximum
length limit for a file it typically should not exceed 1000 lines. This
however is not a hard limit, but it should be used as guideline.
File Naming Convention
File names are made up of a base name and an optional period and suffix. The first name should not exceed 32 characters and the suffix should not exceed 3 letters. These rules should apply to both program files and default file names produced by the program. The filename should reflect the main function in the file. For example if the function in correlateImages() is located in a file then the file should be called correlateImages.c. If the files holds the main function the filename can be called main.c or program.c where program reflects the executable file that is created when the program is compiled.
| Files Names for the Program Files | |
|---|---|
| C source files | *.c |
| C header files | *.h |
| X Pixmap files | *.xpm |
| Bitmaps | *.bpm |
| Makefile | Makefile |
| Shared library | *.so |
| Static library | *.a |
| Yacc source files | *.y |
| Lex source files | *.l |
| Forms Internal files | *.fd |
| Template files | *.tpl |
| Validation Files | *.val |
| Gentle Grammer Files | *.g |
| Directory Summary | README |
| Common IPL Files Names suffixes | |
|---|---|
| Palletes | *.pal |
| Images | *.img, *.pic, *.ima, *.imf, *.raw |
| Headers | *.hdr, *.aux, *.ipl |
| Picture Files | *.tif, *.gif, *.rgb |
| Log Files | *.log |
Program Files
Standard: Program Prologue
A program file should contain a prologue that
provides vital information about the function of the file. It should
contain the MH-CRC Source file line, the copyright, the file name, the
authors name, the date and time of the last revision, a synopsis of the
files purpose, a list of all of the functions in the file, a list of
global variables used in the file, and revision control information in
the development and maintenance of the file. If a program file contains
several functions, the functions should be related in some manner. The
desired relation of functions is based on a "breadth-first"
approach where functions with a similar level of abstraction are
together. For example in the development of a graphical user interface
(GUI), all of the callback functions for a particular dialog box
probably would exist in the same file. The program file prologue shown
below is located in BRAINS2 directory templates/cprologue.tpl.
/!\verbatim***************MH-CRC IPL********************************* * Iowa MH-CRC IPL C Source File * Copyright (C) 1998 Nancy C. Andreasen and Vincent A. Magnotta * \endverbatim * * \file filename * \author authors name * * \date ??/??/???? ??:?? * * \brief Tell general relationship of functions - GUI * Interface Callback functions * * \par Functions: * \arg Function Prototype-1 * \arg Function Prototype-N * * \par Globals: * \arg Global Variable-1 - Short description * \arg Global Variable-N - Short description * * \par Revision History: * \verbatim * Name Date Comments * -------------------------------------------------------------------- * ??? ??/??/??? Fixed Bug * * * \endverbatim **********************************************************************/
Standard: Functional Prologue
Each function must also have a short prologue that
tells what the function does, the arguments for the function, the type
of output for the function, and any global variables used by the
function. The function prologue shown below is located in the BRAINS2
directory called templates/funcprologue.tpl.
/*!\verbatim*********************************************************** * Function: function name() * * \endverbatim * * \author authors name * * \fn Function prototype () * \param param-1 - Description * \param param-N - Description * \return Return values * * \brief Tell general purpose of the function call * add any Refs that accompany the routine * * * \par Globals Used: * \arg Global_Variable1 - Description * **********************************************************************/
The general guideline is that any function should
not exceed 2-3 pages. If the function is longer than this, then the
design of the function should be questioned. This is especially true if
portions of the code could be broken into sub-functions that are
separate entities.
Guideline: Line Length
The length of any line should not exceed 255
characters. In general the length of a line should not exceed 132
characters which will eliminate problems when the program is printed.
If a line is too long then it should be broken into separate pieces.
The line length includes any white space used for indentation and any
comments that may follow the code.
Standard: Program File Layout
Sections within the C-code should appear in this order:
Prologue /* * Include Files */ Include Files /* * Defines */ Defines in order of constant macros, function macros, typedfefs , and enums /* * Global Definitions */ Global Data declarations in the order extern, non-static, and static Function Prologue - 1 function - 1 . . . Function Prologue - N function - N
This program layout template is laid out for the user in templates/c.tpl. This file should be used as the template for all programs.
Standard: Header Files
The header file must begin with a prologue that is similar to the program prologue with a couple of modifications. The header file prologue is shown below. This template is laid out in the file templates/h.tpl or templates/hprologue.tpl. This file should be used as the template for all header files.
/*!\verbatim******************MH-CRC IPL******************************* * Iowa MH-CRC IPL C Header File * Copyright (C) 1998 Nancy C. Andreasen and Vincent A. Magnotta * * \endverbatim * * \file filename * \author authors name * * \date ??/??/???? ??:?? * * \brief Tell general purpose of header variables and * macros * * \par Required Headers: * \arg hdr_name.h - comment about why header is needed * * \par Revision History: * \verbatim * Name Date Comments * -------------------------------------------------------------------- * ??? ??/??/??? Fixed Bug * * * \endverbatim **********************************************************************/
Guideline: Header File Organization
a. All functions in a given header file should be related to the same general function, i.e. declarations for separate sub-systems should be in separate header files. For example, all functions in the header file stdio.h either perform or assist in the performance of input and output.
b. Within a header file, functions that perform related tasks should be grouped in the same section. For example within the file stdio.h, all functions in the scanf family are placed together.
c. A set of declarations that is likely to change
when the code is ported from one machine to another should be in a
separate file.
Guideline: Header File-Naming
a. Avoid private header filenames that are the same as public header filenames.
b. Don't use absolute pathnames for header files. Use the <name> construction for getting them from a standard place.
c. The "include-path" option of the C
compiler (-I on many systems) used in the Makefile is the best way to
handle extensive private libraries of header files; it permits
reorganizing the directory structure without having to alter source
files.
Standard: Header File Inclusion in the File That
Defines the Function
a. Header files that declare functions or external variables must 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.
b. Put code like the following into each .h file to prevent accidental double-inclusion.
#ifndef EXAMPLE_H #define EXAMPLE_H ... /* body of example.h file */ #endif /* EXAMPLE_H */
Guideline: Do Not Nest Header Files
Header files should not be nested. The prologue for
a header file should, therefore, describe what other headers need to be
included for the header to be functional. In extreme cases, where a
large number of header files are to be included in several different
source files, it is acceptable to put all common include statements in
one include file.
Guideline: README Files
It is conventional to have a file called README to document both the big picture
and issues for the program as whole. For this it is
common to include a list of all files in the directory with a list of
functions contained in the files with the proper prototype. All
compilation flags with definitions should be documented along with a
list of all machine dependent files.
Comments
The comments should describe what is happening, how it is being done, and what parameters mean. Avoid comments that are clear from the code. Comments that disagree with the code are of negative value.
Comments should justify any offensive code.
Comments that describe data structures, algorithms, etc. should be in block comment form with the opening /* in columns 1-2 a * in column 2 before each line of comment text, and the closing */ in columns 2-3. An alternative form is to have ** in columns 1-2 and the closing */ in columns 1-2.
/* * Here is a comment block * The comment text should be tabbed or spaced uniformly * The open and closing are alone on a line */ /* ** Alternative form for comment Block */
Note that grep '^.\e*' will catch all of the block comments in the file.
Block comments inside the code are appropriate and they should be tabbed over to the same setting as the code they describe. One-line comments alone on a line should be indented to the tab setting of the code that follows:
if (argc > 1) {
/* Get the input file from the command line */
if (freopen(argv[1],"r",stdin) == NULL) {
perror(argv[1]);
}
}
Very short comments may appear on the same line of
the code they describe, and should be tabbed over to separate them from
the statements. If more than one comment appears in a block of code
they should all be tabbed to the same tab setting.
Variable Declarations
Standards: Variable Naming
a. All variable names must begin with a lowercase letter.
b. The words of a compound variable should be separated by capitalizing the first letter of every word except the first word, e.g. pixelSize. In some cases where it is clearer, variables may be named by separating words with underscores, e.g. pixel_size.
c. The names of pointers should begin with the prefix 'ptr' or 'ptr_', e.g. ptrMyObject or ptr_myObject.
d. Global variables, if used, should have names that begin with the prefix 'gbl' or 'gbl_', e.g. gblPixelScale or gbl_pixelScale.
e. Variable names should be meaningful and should avoid abbreviations except where extraordinarily long file names would occur. This help to improve readability of the code a later date or by another programmer who does not understand your abbreviations.
f. Defined names must be all upper case separated by underscores, e.g.
#define WEEK_DAYS 7
g. In the BRAINS2 project no assumption about the size of variables will be made. This should increase the portability of the code possibly in the future. A header file called b2Types.h has been created which contains the following data types that will be guaranteed to be the desired size for the compilation platform:
Type Size Min Max S8 8 bit S8_MIN S8_MAX U8 8 bit U8_MIN U8_MAX S16 16 bit S16_MIN S16_MAX U16 16 bit U16_MIN U16_MAX S32 32 bit S32_MIN S32_MAX U32 32 bit U32_MIN U32_MAX S64 64 bit S64_MIN S64_MAX U64 64 bit U64_MIN U64_MAX F32 32 bit float F32_MIN F32_MAX F64 64 bit float F64_MIN F64_MAX C32 2x32 bit float F32_MIN F32_MAX C64 2x64 bit float F64_MIN F64_MAX
Guideline: Do Not Use Global Variables
The use of global variables is strongly discouraged.
It is conceivable, however, that there will be cases where the use of
global variables can actually make a program more readable by not
cluttering function calls. Any use of global variables by a function
should be documented in the prologue. Note: often the use of global
variable may make a function much less flexible in the long run.
Standard: Global Variables Declaration at Top of
File
Any global variables must be declared at the top of a file, before any function declarations. Variables which are global to only the functions in a single file should be declared as "static". The ordering of global variables in the file should be extern, non-static, and static. This is shown in section 2.2.4.
Global declarations should begin in column 1. All external data declaration should be preceded by the extern keyword. 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.
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. The names, values, and comments are usually tabbed so that they line up underneath each other. Use the tab character rather than blanks (spaces). For structure and union template declarations, each element should be alone on a line with a comment describing it. The opening brace ({) should be on the same line as the structure tag, and the closing brace (}) should be in column 1.
struct BOAT {
int wllength; /* water line length in meters*/
int type; /* see below */
long sailarea; /* sail area in square mm */
};
/* defines for boat.type */
# define KETCH (1)
# define YAWL (2)
# define SLOOP (3)
# define SQRIG (4)
# define MOTOR (5)
These defines are sometimes put right after the declaration of type, within the struct declaration, with enough tabs after the # to indent define one level more than the structure member declarations. When the actual values are unimportant, the enum facility is better.
enum bt { KETCH=1, YAWL, SLOOP, SQRIG, MOTOR };
struct BOAT {
int wllength; /* water line length in meters*/
enum bt type; /* what kind of boat */
long sailarea; /* sail area in square mm */
};
Any variable whose initial value is important should be explicitly initialized, or at the very least should be commented to indicate that C's default initialization to zero is being relied upon. The empty initializer, "{ }" should never be used. Structure initializations should be fully parenthesized with braces. Constants used to initialize longs should be explicitly long. Use capital letters; for example, "2l" (two long) looks a lot like "21" (the number twenty-one):
int x = 1;
char *msg = "message";
struct BOAT winner[] = {
{ 40, YAWL, 6000000L },
{ 28, MOTOR, 0L },
{ 0 },
};
Standard: Local Variable Declaration at Beginning
of Function
a. Variables within a function must be declared at the beginning of the function.
b. C does permit the declaration of variables within any block, e.g., within a "for" block, but this practice will not be allowed in IPL programs
c. 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.
Standards: Use #DEFINE for Symbolic Constants
a. All quantities that must remain unchanged throughout a program must be named using the "#define" capability. The defined name must be in upper case letters. For example:
#define MAX_CHANNELS 4096
b. Symbolic constants that are used only in a single
compiland should be defined at the beginning of that compiland.
Symbolic constants that have a more general scope should be defined and
documented in header files of appropriate scope.
Guideline: Variable Initialization in Declaration
a. Static variables in a function that are initialized in the declaration are set once before the beginning of execution and maintain their assigned value whenever the function is called. If not initialized in the declaration, static variables are defined to be zero initially.
b. Automatic variables in a function that are
initialized in the declaration are set to the same value whenever the
function is called. If not initialized in the declaration, automatic
variables are undefined and probably garbage.
Standard: Explicit +1 in String Length
Declaration for \n
Character arrays used as strings, i.e., to hold ASCII text and terminated by a null character) should have a defined length that explicitly includes the "+ 1" character for the null string terminator.
#define NAME_LEN 20 + 1 char name[NAME_LEN];
Standards: Structure Declaration
a. Each field in a structure must be declared on a separate line.
b. The structure must have a tag, in upper case.
c. The actual assignment of a structure to a variable must be done in a separate statement.
struct BOOK {
char name[NAME_LEN];
char author[AUTHOR_LEN];
long number;
};
struct BOOK goneWithTheWind;
Standard: Use Conventional Prefixes for Variables
| Variable Function | Variable Prefix Abbreviation |
| average | avg |
| database | db |
| length | len |
| message | msg |
| number | num |
| pointer | ptr |
| position | pos |
| string | str |
| object | obj |
| form | fd |
| summation | sum |
Guideline: Brains2 Function naming convention
For the BRAINS2 project standard function call naming convention exists to allow easy inspection of what the function operates on. This should help to improve the readability and maintainability of the code. Shown below is a table of the function prefixes that are preferred for the BRAINS2 project
| Function Type | Function Prefix |
| BRAINS2 Object Functions | ob |
| Queue Function Calls | que |
| BRAINS Kernel Calls | bk |
| Standard Image Lab Routines | ipl |
| Private Object Functions | pv |
Guideline: Macros
a. Macro names should be all upper case characters.
b. Include a comment on the same line as macro declarations.
c. Place all macro definitions at the beginning of the file after the prologue, or in a header file.
d. Place all shared macros in a header file.
e. Place parentheses around the parameters in the replacement text and around the entire text whenever possible. For example:
#define NEXT(p) ((p)->_next)
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.
Some macros also exist as functions (e.g., getc and fgetc). The macro should be used in implementing the function so that changes to the macro will be automatically reflected in the function. Care is needed when interchanging macros and functions since function parameters are passed by value, while macro parameters are passed by name substitution. Carefree use of macros requires that they be declared carefully.
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:
#define OFF_A() (glb_a+OFFSET)
#define BORK() (zork())
#define SP3() if (b) { int x; av = f (&x); bv += x; }
Macros save function call/return overhead, but when a macro gets long, the effect of the call/return becomes negligible, so a function should be used instead.
In some cases it is appropriate to make the compiler insure that a macro is terminated with a semicolon.
if (x==3) SP3(); else BORK();
If the semicolon is omitted after the call to SP3, then the else will (silently!) become associated with the if in the SP3 macro. With the semicolon, the else doesn't match any if ! The macro SP3 can be written safely as
#define SP3() \\\\
do { if (b) { int x; av = f (&x); bv += x; }} while (0)
Writing out the enclosing do-while by hand is awkward and some compilers and tools may complain that there is a constant in the while conditional. A macro for declaring statements may make programming easier:
#ifdef lint
static int ZERO;
#else
# define ZERO 0
#endif
#define STMT( stuff ) do { stuff } while (ZERO)
Declare SP3 with
#define SP3() \\\\
STMT( if (b) { int x; av = f (&x); bv += x; } )
Using STMT will help prevent small typos from silently changing programs.
Except for type casts, sizeof, and hacks such as the
above, macros should contain keywords only if the entire macro is
surrounded by braces.
Functions
Function Declarations
Standards: Return Values Must Be Explicitly
Declared
a. Return values must be explicitly declared.
b. The function declaration should return void if an
actual value is not being returned, and there is no possible way for
the function to fail.
Standards: Parameter List
a. If the function and its parameter list is longer than one line, lines after the first one will be indented from the left margin so that the second line of the parameter list starts directly below where the parameter list begins on the first line.
b. The list of function parameters should have a definite order. The standard for the C library functions is opposite from most other languages and from most programmer's intuition. For example, the string copy function, strcpy(out, in), takes the output parameter first and then the input parameter. This makes sense if you think of the ordering like you think of the ordering of an assignment statement. We can't change the C library standard but for routines which we write, we will use the more conventional parameter ordering of input, modified (input and output), and finally output parameters.
c. A function that returns information via one or more of its parameters may return only status information in its name.
d. For the BRAINS2 project if a function is returning a value giving only the status of the call then it should be of type STATUS with the two following return values SUCCESS and FAILURE.
e. Be careful when you use or declare functions that take a variable number of arguments ("varargs"). There is no truly portable way to do varargs in C. Better to design an interface that uses a fixed number of arguments. If you must have varargs, use the library macros for declaring functions with variant argument lists.
f. If the function uses any external variables (or functions) that are not declared globally in the file, these should have their own declarations in the function body using the extern keyword.
g. Avoid local declarations that override
declarations at higher levels. In particular, local variables should
not be redeclared in nested blocks. Although this is valid C, it is
potentially very confusing in debugging.
Standard: Static Function
Any function which is only called from other
functions in the same file should be declared "static".
Template for Function Declarations
void func_name (int firstParam,int secondParam, int thirdParam)
{
int i; /* Loop variable */
int sum; /* Loop sum */
sum = 0;
for (i=0;i < firstParameter;i++)
{
sum += secondParameter;
}
printf("%d %d %d %d\n", firstParam, secondParam,
thirdParam, sum);
return;
}
Standards: Function Body
a. The function body, until the closing brace will be indented one step (three or four spaces - do not use tabs). A beginning brace, "{", opening the body of the function must be on a line by itself and left justified or at the end of the line which introduces the block. course, any control statements will cause further indentation from this basic indentation.
b. A return statement must always be present with a parameter if the function is not of type void. Even if the function has no return value and, therefore the C language does not require a return, it is good practice to use one. This can be useful for setting a break point during debugging.
c. A closing brace, "}", closing the body
of the function must be on a line by itself and left justified.
Standards: Function Prototype
a. Function prototypes must be generated for all functions generated.
b. If the function prototypes reside in a separate
file, that file should have the extension of ".h".
Statements
Guideline: One Statement Per Line
There should be only one statement per line unless the statements are very closely related. For example,
if (fail == 1) {print_error(); return FAILURE}
Guideline: Do Not Use Goto and Continue Statements
"Goto" and "continue" statements
are highly discouraged.
Guideline: Exit Statement Discouraged
Explicit use of the "exit();"
statement is not allowed, except for explicitly predefined functions
whose purpose are program termination.
Guideline: Maximum 4 Levels of Control Structure
Nesting
Nesting of statements, "if",
"for", "while", etc., should go no more than 4
levels. If more levels appear to be needed, consider use of a function
at one of the higher levels. This guideline should be followed for most
functions, however there may be several cases, especially with images,
where more nesting is required. In these cases, nesting of the control
structure is allowed to be greater than 4 levels.
Standards: Null Statements
a. Null statements must include a comment line. For example, if the "default:" case in a switch statement does nothing, put in a "default:" label followed by a comment to the effect: /* no action*/ followed by the break statement.
b. The null body of a "for" or "while" loop should be alone on a line and commented so that it is clear that the null body is intentional and not missing code:
while (*dest++ = *src++) ; /* VOID */
Standard: Comment for Missing "break"
in switch
If a particular case in a switch statement is meant to drop through to the next case (i.e., it has the same effect), the fact that the earlier case has no "break" statement should be explicitly noted with a comment:
switch(whichIsotope) {
case PU239 : /* same action as for PU240; no break */
case PU240 :
processPlutoniumIsotope();
break;
default :
break; /* if not PU 239 or 240, no action taken */
}
Standards: Statement Blocks
a. Statements that affect a block of code (i.e., more than one statement) must either have the opening brace "{" at the end of the line containing the control statement, or the opening brace must be on the line immediately below and lined up with the first letter of the control statement.
b. The body of the block must be indented one step from the control statement.
c. The ending brace, "}", must be on a line by itself and at the same indentation level as the control statement.
d. Examples:
if (first < last) {
resultOne = first / 2;
resultTwo = last / 2;
}
else {
resultOne = last / 2;
resultTwo = first / 2;
}
do
{
status = doSomething();
} while (status == SUCCESS);
Guideline: "block" with Single Statement
The programmer is encouraged to block off even a single statement following a "while", "if", "else", etc. Using the curly braces "{}" is required only when there is a block of more than one statement. However, putting in the braces makes the scope of the control statement very clear and helps to protect the code in the event that a second line is added to the block if the single line contains a macro which translates into more than one line of code.
if (someCondition == TRUE) {
thisVariable = thatVariable;
}
Standard : Increment and Decrement Operator Only
as Postfix
The increment and decrement operators, "++", and "--" are permitted only in post-fix notation, e.g. "i++", and not as part of any statement. For example, the code block
while (string[i++] != NULL) {
doSomething();
}
is not allowed; the preferred style is
while (string[i] != NULL) {
doSomething();
i++
}
Standard: Branching on Function Call
Often a program will branch based on the success or failure of a function call. It is clearer to break out the function call onto a separate line followed by a new line containing the conditional statement:
ptr_FileHandle = fopen("some_file", READ_ONLY);
if (ptr_FileHandle == NULL) {
printf("Could not open file; program terminating.");
terminateApplication();
}
else {
doSomething();
}
is easier to understand than:
if ((fileHandle = open("some_file", READ_ONLY))== NULL) {
printf("Could not open file; program terminating.");
terminateApplication();
}
else {
doSomething();
}
The "fopen" example above demonstrates a
style which helps to avoid internal side effects. "Side
effect" as used in this example refers to the fact that the reader
may focus on the "if(xxx == NULL)" aspect of the
statement and not fully realize that there is a call to "fopen()".
The other danger of this type of compound statement is the potential
for completely changing the meaning if the parentheses around the "fileHandle
= open()" part are left off.
Standard: Avoid Internal Side Effects
Expressions may not produce any internal side effects. For example the expression
while(string[i++]) != 0)
should not be used because C's order of evaluation is explicitly undefined.
Standard: Do Not Use Default Truth Value Test
Do not default the test for non-zero. For example, this
check = f(); if (check != FAIL)
is better than this
check = f(); if (check)
Operators
Standard: Use Single Spaces Around Binary Operator
All operators which take two parameters must have a
single space on either side of the operator. This makes it very handy
to use an editor to search for a variable assignment; you need only
search for "a =" rather than both "a ="
and "a=". It also makes the code more readable.
Standard: No Space Following Unary Operator
In contrast to binary operators, all unary operators
(e.g., a minus sign or the address operator, "&") should
not have a space between the operator and the object.
Guideline: Conditional Operator
The use of the ternary conditional operator, "?:"
should be used in functions only in cases where it makes the code more
readable. If it makes the meaning less clear, then its use is
discouraged. Use of the ternary conditional operator is acceptable in
macros.
Standard: Use Parentheses to Remove Precedence
Ambiguity
Where operator precedence must be known to determine
the meaning of an expression, it is required that you use parentheses
to eliminate any ambiguity which might arise from lack of knowledge of
operator precedence. For example, to increment the variable pointed to
by the pointer "ptr_NumTimes", use "(*ptr_NumTimes)++".
This use of parentheses makes it clear that the contents of location
"ptr_NumTimes" is being incremented and not the
address itself.
White Space
Guideline: Use of Whitespace for Readability
Use vertical and horizontal whitespace judiciously
to make the program more readable. Indentation and spacing should
reflect the block structure of the code.
Standard: Vertical Spacing of Conditional
Operators on Separate Lines
A long string of conditional operators should be split onto separate lines.
if (foo-<next==NULL && totalCount < needed
&& needed<=MAXLLOT && serverActive(currentInput)) { ...
will be better as
if (foo-<next == NULL
&& totalCount<needed
&& needed<= MAXLLOT
&& serverActive(current -input))
{
...
Similarly, elaborate "for" loops should be split onto different lines.
for (curr = *listp, trail = pList;
curr != NULL;
trail = &(curr->next), curr = curr->next) {
doSomething();
doAnotherSomething();
}
Standard: Spacing for Parentheses
Keywords that are followed by expressions in
parentheses should not be separated from the left parenthesis. Also put
blanks after commas in argument lists to help separate the arguments
visually.
Constants
Standard: Naming Symbolic Constants in Upper Case
Symbolic constants must be in upper case. e.g.,
"TRUE".
Guideline: Consistency of Constant Definitions
Constants should be defined consistently with their
use; e.g. use 540.0 for a double instead of 540 with an implicit float
cast.
Standard: Conventional Constants
A conventional set of symbolic constants will be
used for constants common to C coding:
| TRUE | anything but zero, the value 1 will be used |
| FALSE | zero |
| LF_CHAR | line feed character (10) |
| CR_CHAR | carriage return character (13) |
| EOS | end of string character ('\0') |
| EOL | end of line character ( ) |
| EOF | end of file |
| FAILURE | error returned from function with type STATUS |
| SUCCESS | successful completion of a function of type STATUS |
Conditional Compilation
Guideline: Use of Conditional Compilation
Conditional compilation should only be used for
controlling the compilation of machine-dependent code, setting
optimizations at compile time, and debugging.
Standard: Default of Conditional Compilation
If a conditional compiland is used to control
machine dependencies code, then the behavior when no machine is
specified should result in an error and not a default machine. The
default for a conditional compiland meant to allow for code
optimization should be un-optimized code.
Guidelines: Conditional Compilation
a. Whenever possible, put conditionally compiled code in a header file instead of in the main program.
b. Conditional compilands should be bracketed on a feature-by-feature basis.
c. Debugging statements in the code that are left in for future debugging or addition of features should be set of by the conditional block
#ifdef DEBUG fprintf(stderr,"Debugging line\n"); #endif
This will allow for easy debugging in the future
just by defining the DEBUG macro.
Portability
Standards: Machine-Dependent Code in Separate File
a. Place all machine-dependent code in a separate file from all machine-independent code.
b. Machine-dependent code must be
"#ifdef'ed" so that an informative error message will result
if the code is compiled on a machine other that which it is designed
for.
Guideline: Machine-Dependent Code Use
a. Only write machine-dependent code when necessary. Even if, for example, a particular piece of hardware requires that a machine-dependent routine be written, try to write any routines that support the machine-dependent code machine-independently.
b. 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
c. Organize source files 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. Comment the
machine dependence in the headers of the appropriate files.
General Guidelines: Portability
a. Try not to assume ANSI. Even though ANSI-compliant code should be written, if you know that many non-ANSI compilers will not understand some code and you know of a more portable way of writing the code which is still ANSI-compatible, do so.
b. Be aware that the size of different data types may vary from platform to platform. Be especially careful to avoid making assumptions about integers and pointers.
c. Also be aware that the precision and storage format of floating point numbers may vary from platform to platform.
d. Do not assume that software will always be executed on the machine for which it is originally designed.
e. The void* type is guaranteed to have enough bits of precision to hold a pointer to any data object. The void(*)() type is guaranteed to be able to hold a pointer to any function. Use these types when you need a generic pointer. (Use char* and char(*)(), respectively, in older compilers). Be sure to cast pointers back to the correct type before using them.
f. On ANSI compilers, when two pointers of the same type access the same storage, they will compare as equal. When non-zero integer constants are cast to pointer types, they may become identical to other pointers. On non-ANSI compilers, pointers that access the same storage may compare as different. The following two pointers, for instance, may or may not compare equal, and they may or may not access the same storage.
((int *) 2 ) ((int *) 3 )
If you need `magic' pointers other than NULL, either allocate some storage or treat the pointer as a machine dependence.
extern int x_int_dummy; /* in x.c */ #define X_FAIL (NULL) #define X_BUSY (&x_int_dummy) #define X_FAIL (NULL) #define X_BUSY MD_PTR1 /* MD_PTR1 from "machdep.h" */
g. 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. If absolutely necessary, machine-dependent code should be #ifdeffed or operations should be performed by #ifdeffed macros. You should weigh the time savings with the potential for obscure and difficult bugs when your code is moved.
h. Data alignment is also important. For instance, on various machines a 4-byte integer may start at any address, start only at an even address, or start only at a multiple-of-four address. Thus, a particular structure may have its elements at different offsets on different machines, even when given elements are the same size on all machines. Indeed, a structure of a 32-bit pointer and an 8-bit character may be 3 sizes on 3 different machines. As a corollary, pointers to objects may not be interchanged freely; saving an integer through a pointer to 4 bytes starting at an odd address will sometimes work, sometimes cause a core dump, and sometimes fail silently (clobbering other data in the process). Pointer-to-character is a particular trouble spot on machines which do not address to the byte. Alignment considerations and loader peculiarities make it very rash to assume that two consecutively-declared variables are together in memory, or that a variable of one type is aligned appropriately to be used as another type.
i. There may be unused holes in structures. Suspect unions used for type cheating. Specifically, a value should not be stored as one type and retrieved as another. An explicit tag field for unions may be useful.
j. 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.
k. Do not make assumptions about the parameter passing mechanism especially pointer sizes and parameter evaluation order, size, etc. The following code, for instance, is very nonportable.
c = foo (getchar(), getchar());
char
foo (c1, c2, c3)
char c1, c2, c3;
{
char bar = *(&c1 + 1);
return (bar); /* often won't return c2 */
}
This example has lots of problems. 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.
l. On some machines, the null character pointer ((char *)0) is treated the same way as a pointer to a null string. Do not depend on this.
m. Do not modify string constants. One particularly notorious (bad) example is
s = "/dev/tty??"; strcpy (&s[8], ttychars);
n. The address space may have holes. Simply computing the address of an unallocated element in an array (before or after the actual storage of the array) may crash the program. If the address is used in a comparison, sometimes the program will run but clobber data, give wrong answers, or loop forever. In ANSI C, a pointer into an array of objects may legally point to the first element after the end of the array; this is usually safe in older implementations. This "outside" pointer may not be dereferenced.
o. Only the == and != comparisons are defined for all pointers of a given type. It is only portable to use <<, <=, >, or >= to compare pointers when they both point in to (or to the first element after) the same array. It is likewise only portable to use arithmetic operators on pointers that both point into the same array or the first element afterwards.
p. 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 &= 0177770. Use instead x &= ~07 which works properly on all machines. Bitfields do not have these problems.
q. Side effects within expressions can result in code whose semantics are compiler-dependent, since C's order of evaluation is explicitly undefined in most places. Notorious examples include the following.
a[i] = b[i++];
In the above example, we know only that the subscript into b has not been incremented. The index into a could be the value of i either before or after the increment.
struct bar_t { struct bar_t *next; } bar;
bar->next = bar = tmp;
In the second example, the address of bar->next may be computed before the value is assigned to bar.
bar = bar->next = tmp;
In the third example, bar can be assigned before bar->next. Although this appears to violate the rule that ``assignment proceeds right-to-left,'' it is a legal interpretation. Consider the following example:
long i; short a[N]; i = old i = a[i] = new;
The value that i is assigned must be a value that is typed as if assignment proceeded right-to-left. However, i may be assigned the value ``(long)(short)new'' before a[i] is assigned to. Compilers do differ.
r. Avoid preprocessor tricks. Tricks such as using /**/ for token pasting and macros that rely on argument string expansion will break reliably.
#define FOO(string) (printf("string = %s",(string)))
...
FOO(filename);
Will only sometimes be expanded to
(printf("filename = %s",(filename)))
Be aware, however, that tricky preprocessors may cause macros to break accidentally on some machines. Consider the following two versions of a macro.
#define LOOKUP(chr) (a['c'+(chr)]) /* Works as intended. */ #define LOOKUP(c) (a['c'+(c)]) /* Sometimes breaks. */
The second version of LOOKUP can be expanded in two different ways and will cause code to break mysteriously.
s. Become familiar with existing library functions and defines. (But not too familiar. The internal details of library facilities, as opposed to their external interfaces, are subject to change without warning. They are also often quite unportable.) You should not be writing your own string compare routine, terminal control routines, or making your own defines for system structures. ``Rolling your own'' wastes your time and makes your code less readable, because another reader has to figure out whether you're doing something special in that reimplemented stuff to justify its existence. It also prevents your program from taking advantage of any microcode assists or other means of improving performance of system routines. Furthermore, it's a fruitful source of bugs. If possible, be aware of the differences between the common libraries (such as ANSI, POSIX, and so on).
t. Use explicit casts when doing arithmetic that
mixes signed and unsigned values.
ANSI C
Standard: ANSI-Compatible Code
All code should conform to the standard established
by ANSI.
Guideline: Compiler
All C code should be compiled and tested with the
both the cc and gcc compilers whenever possible.
Standard: POSIX
Make standard POSIX compatible functional calls
whenever possible. Non-standard machine specific functional calls
should be commented and stated why they are being used.
Miscellaneous
Standard: Always Check Function Return Value
Always check the return values of functions which
return a special value when an error occurs. If there really were no
chance of an error occurring, then the function would not have such a
return value.
Guideline: Use Library Functions When Possible
Use functions in libraries whenever possible instead
of re-inventing the wheel.
Guideline: Lint
Lint is a C program checker that examines C source files to detect and report type incompatibilities, inconsistencies between function definitions and calls, potential program bugs, etc. The use of lint on all programs is strongly recommended, and it is expected that most projects will require programs to use lint as part of the official acceptance procedure.
It should be noted that the best way to use lint is not as a barrier that must be overcome before official acceptance of a program, but rather as a tool to use during and after changes or additions to the code. Lint can find obscure bugs and insure portability before problems occur. Many messages from lint really do indicate something wrong. One fun story is about is about a program that was missing an argument to "fprintf:"
fprintf ("Usage: foo -bar <file>\n");
The author never had a problem. But the program dumped core every time an ordinary user made a mistake on the command line. Many versions of lint will catch this.
Most options are worth learning. Some options may complain about legitimate things, but they will also pick up many botches. Note that `--p' checks function-call type-consistency for only a subset of library routines, so programs should be linted both with and without --p for the best ``coverage''.
Lint also recognizes several special comments in the
code. These comments both shut up lint when the code otherwise makes it
complain, and also document special code.
Guideline: IPL module Certification Procedure
The IPL module certification procedure is explain in
the "IPL Module Certification Procedure" document. The basis
of the document explains what steps are nessecary to have your module
certified by the IPL which will then allow it to become part of the
BRAINS2 software in the future. It is strongly urged that any module
which you feel could be used by another individual in the future be
certified. This is especially true for any software which is or may be
for standard workups.
Guideline: CVS
CVS is a GNU utility that facilitates the ability to
track changes and revisions in source code. While the IPL does not
require that the CVS be used during initial development and testing of
a module especially in experimental code, any module that becomes
certified through the IPL module certification procedure should then be
added to the CVS archive to allow maintenance and tracking of edits on
the certified module. While this manual will not go into the use of CVS
a future document will be written to explain CVS and the IPL's use of
the program.
Special Considerations
This section contains some miscellaneous do's and don'ts.
a. Don't change syntax via macro substitution. It makes the program unintelligible to all but the perpetrator.
b. 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 test floating-point numbers as <= or >=, never use an exact comparison (== or !=).
c. Compilers have bugs. Common trouble spots include structure assignment and bitfields. You cannot generally predict which bugs a compiler has. You could write a program that avoids all constructs that are known broken on all compilers. You won't be able to write anything useful, you might still encounter bugs, and the compiler might get fixed in the meanwhile. Thus, you should write "around" compiler bugs only when you are forced to use a particular buggy compiler.
d. Do not rely on automatic beautifiers. The main person who benefits from good program style is the programmer him/herself, and especially in the early design of handwritten algorithms or pseudo-code. Automatic beautifiers can only be applied to complete, syntactically correct programs and hence are not available when the need for attention to white space and indentation is greatest. Programmers can do a better job of making clear the complete visual layout of a function or file, with the normal attention to detail of a careful programmer. (In other words, some of the visual layout is dictated by intent rather than syntax and beautifiers cannot read minds.) Sloppy programmers should learn to be careful programmers instead of relying on a beautifier to make their code readable.
e. Accidental omission of the second = of the logical compare is a problem. Use explicit tests. Avoid assignment with implicit test.
abool = bbool;
if (abool) { ...
When embedded assignment is used, make the test explicit so that it doesn't get ``fixed'' later.
while ((abool = bbool) != FALSE) { ...
while (abool = bbool) { ... /* VALUSED */
while (abool = bbool, abool) { ...
f. Explicitly comment variables that are changed out of the normal control flow, or other code that is likely to break during maintenance.
g. Modern compilers will put variables in registers
automatically. Use the register sparingly to indicate the variables
that you think are most critical. In extreme cases, mark the 2-4 most
critical values as register and mark the rest as REGISTER. The latter
can be #defined to register on those machines with many registers.
Conclusion
The standards and guidelines laid forth in this
document should be followed zealously and in good faith (do not, for
example, ignore guidelines just because they do not say must). Remember
above all that you are working on a team of programmers, and should
therefore labor to make your code as easy for another to follow as
possible in case another person has to modify your program. It is not
unheard of for even the person who writes sloppy code to not be able to
follow it after a significant amount of time has passed. Finally the
style laid out in this document must be followed in order to certify
the module into the BRAINS2 software (see BRAINS2 Module Certification
Document).
References
1.Spencer, Keppel, and Brader, Recommended C Style and Coding Standards, Revision 6.0, June 25, 1990.
2.The Boulder Software Group, Example C Style Guidelines, 1990.
3. Cannon LL, Elliot RA, Kirchhoff LW, Miller JH, Milner JM, Mitze RW, Schan EP, Wittington NO, et al. Recommended C Style and Coding Standards: Update of the Indian Hills C Style and Coding Standards
4. Los Alamos National Labratory, SDM Style and Coding Standards for the SDM Project, 1996.