CSc 352: C Coding Guidelines
The coding guidelines we will follow are based on GNU coding styles,
with some modifications based on personal taste. Your
programs will be expected to follow these guidelines unless explicitly
directed otherwise.
0. Program Structure
The design of a procedural program is quite different from that of an
object-oriented one. A very important aspect of program design is that
the code should be written in a way that makes it easy to be read and
understood by others. To develop good habits in this regard, we'll
follow the following (rather rigid) set of rules:
-
The main() routine in a program should consist simply of a
series of calls to other functions, each of which carries out some high-level
logical aspect of the computation, possibly with a little bit of control
flow around them. The point is that someone reading the program
should be able to understand the overall logic of the program by looking
at main().
-
Each function in the program should have a comment before it stating what
it does.
In general, each function should do one thing. If you find that
a function is doing several different things, or that you're having
trouble succinctly summarizing what a function does, you may want to
reconsider the structure of the program. (This doesn't mean that it's
never OK to have one function do more than one thing: but if you
have a function doing many things, you should treat that as a warning flag,
and its design should be carefully thought about and defended).
1. Robustness
2. Error Messages
-
Error messages should always be printed to stderr. They should,
at the very minimum, indicate the nature of the problem. For example,
if a system call has failed, indicate which system call and the nature
of the failure (via perror).
3. Comments
4. Clean Use of C Constructs
-
Explicitly declare the types of all objects. For example, you should explicitly
declare all arguments to functions, and you should declare functions to
return int rather than omitting the int and
relying on the default.
-
Declarations of external functions and functions to appear later
in the source file should all go in one place near the beginning of the
file (somewhere before the first function definition in the file), or else
should go in a header file. Don't put extern declarations inside functions.
-
Header files should not contain function definitions: such definitions
should be in *.c files.
-
Try to avoid assignments inside if-conditions. For example, don't
write this:
if ((foo = (char *) malloc (sizeof *foo)) == 0)
fatal ("virtual memory exhausted");
instead, write this:
foo = (char *) malloc (sizeof *foo);
if (foo == 0)
fatal ("virtual memory exhausted");
5. Naming Files, Variables and Functions
-
The name of a file should give some indication of the purpose of the code
in that file. If you're having trouble coming up with a descriptive
name for a file because the code in it has several different purposes,
this may be an indication that, from an organizational perspective, you
should split the code in the file into multiple files.
-
The names of global variables and functions in a program serve as comments
of a sort. So don't choose terse names--instead, look for names that give
useful information about the meaning of the variable or function. In a
GNU program, names should be English, like other comments.
-
Local variable names can be shorter, because they are used only within
one context, where (presumably) comments explain their purpose.
6. Information Hiding
When you declare a struct or union, you should also define
macros to access its fields. Avoid accessing the fields directly
in the main body of the program: instead, use these macros for accessing
the fields. Thus, avoid writing code like this:
struct tn {
int ntype;
union {
int nval;
char *name;
struct tn *child[2];
} flds;
} *tptr;
...
if (tptr->ntype == EXPR_PLUS) {
emit_code(tptr->flds.tn[0]);
emit_code(tptr->flds.tn[1]);
}
Instead, write
struct tn {
int ntype;
union {
int nval;
char *name;
struct tn *child[2];
} flds;
} *tptr;
#define Type(x) ((x)->ntype)
#define Value(x) (((x)->flds).nval)
#define Name(x) (((x)->flds).name)
#define Child(x,i) (((x)->flds).child[i])
...
if (Type(tptr) == EXPR_PLUS) {
emit_code(Child(tptr,0));
emit_code(Child(tptr,1));
}
The latter style is easier to understand (e.g., it's easier to see that
Child(tptr,0) refers to the 0th child of the node
that tptr points to than tptr->flds.tn[0]);
and changes in the data structure, e.g., to improve performance, are easier
to accommodate and less prone to bugs.
Acknowledgements
The material in this document is based on GNU coding styles.