A List Of Rules That Every C Programmer Should Follow
Here’s a list of rules that every C programmer should follow. Occasionally, a rule may be violated, but only for a really good
reason.
- Most standard functions have a header associated with them, which should always be included. The manuals often fail to identify what this header is, although it’s there in “/usr/include” (or wherever). You might consider grepping the header files to discover what headers go with what functions. This rule means that most C files will start with 5 to 10 includes. Don’t feel weird if some of your files start with even more.
- Never declare a function if it’s declared in a header, as redundant declarations lessen reliability, portability, and maintainability. If you discover that some standard functions aren’t in any header, you might consider coding a header so you can just include it all the time.
- Always declare the return value of a function when the function is defined, even if it’s “int”. This makes your intent clear. It also avoids letting a function default to “int” when it should have been declared “void”.
- For functions internal to a file (and, therefore, not declared in a header), order them so that redundant declarations aren’t necessary. That is, order them as a Pascal program would be, with “main” at the end. Only in the case of mutual recursion is this impossible.
- Always call “exit(0)” at the end of “main”.
- Make all variables and functions “static”, unless they are external to the file.This helps the reader by indicating thelocality of the object, and it also avoids name clashes in the linker.
- Don’t use “int”, “0”, and “1” for Boolean operations, as these are needlessly general. Instead, use these:
typedef int BOOLEAN;
#define FALSE 0
#define TRUE 1
- Where a logical expression is required (“if”, “while”, etc.), do not write an integer expression. That is, do this:
if (*p != ”) not this: if (*p)
- This is done to speed up debugging. When the second technique is used, mistakes are often made because the negation of the correct expression is accidentally written. This bug is perhaps most frequent with non-intuitive return values, such as the one from “strcmp”.
- Never call a function without checking its error return. If the error is impossible, at least call “assert”, as in this example: if (close(fd) == -1) assert(FALSE);
- Check null pointers against NULL, never against 0 or anything else. Do not cast NULL: if (flurb(x, y) == (struct Rcd *)NULL) /* bad */ …
- When you have an initialization, a test, and an incrementation, use a “for” loop instead of a “while” loop. This makes the loop control more explicit and reduces the possibility of forgetting to increment.
- When calling a function that returns a useful value or an error return, use this “assign and check” paradigm:
if ((p = fcn(x, y)) == NULL)
… handle error …
- While ordinarily I would recommend against doing assignment in the middle of an expression, this is an exception. It occurs so often that it’s immediately recognizable to the experienced reader, requiring no time to figure out. Non-experienced readers should learn about it.
- Never read anything from a human user without checking it thoroughly for errors and reporting problems back precisely. For example, if the user hits Control-D or Control-Z, don’t give a “conversion error” message. This rule prohibits the use of “scanf” in most cases.
- Don’t use lousy functions just because they’re in the library. What some library contributors know about programming you could fit in your left ear. This rules out functions like “atol”, which reports no error.
- Do not put in gratuitous comments, because it cheapens the value of all comments. Then the reader gets into the habit of skipping comments, and, when you have something important to communicate, there’s no way to do it. Also, comments make the code harder to scan, so they have to be really useful for the reader to want to pay the price.
Here’s an example. First, a gratuitous comment:
/* get input from tape drive */
if (readtape() == ERROR)
…
Now, a useful comment:
/* can’t use gettape() — hangs system when buffered */
if (readtape() == ERROR)
…
- Except for the numbers -1, 0, and, maybe, 1, always use a symbolic constant. Do not dimension arrays with numbers like 81, 257, etc. For string lengths, adopt a convention and stick with it. I use a symbolic constant to define the length exclusive of the NUL byte, and then add one for dimensioning. The alternative is, of course, also OK.
- Every “switch” statement should have a “default” case, to reduce debugging. If it can’t ever occur, use “assert”:
switch (x) {
…
default:
assert(FALSE);
}
- If your compiler supports function prototypes, always use them, both for library functions and for your own. They are the greatest contribution to C programming since the curly brace. I’ve found this paradigm useful for static functions, as it makes modifications easier:
static BOOLEAN fcn(int, float);
static BOOLEAN fcn(count, val)
int count;
float val;
{
…
}
Note that come compilers are set up to ignore library prototypes by default. For example, with Microsoft C you have to define the symbol “LINT_ARGS”.
- Find out what compiler options generate the most warnings and the strictest error checking, and turn them on. Make sure all of your files compile with no warnings at all, or else you’ll miss a new warning because it’s mixed in with the old ones. Doing this will often mean that you’ll use lots of casts. But don’t just blindly put in a cast to make a warning go away, or you might overlook an actual bug.
- How you lay out your programs is a matter of personal preference. But at least be consistent, or you risk telling the reader that you don’t know what you’re doing. Either put a statement on the same line as the “if”, or on the next line, but don’t do it sometimes one way and sometimes the other. Put blanks between a function name and the left parenthesis, or don’t, but always do it the same way. Since all bugs are introduced during text editing, that’s when you need a consistent style. So, don’t use a C beautifier, unless it’s to fix up a program someone else gave you.
- Call “assert” as often as you have the energy to. Don’t define “NDEBUG” to remove assertions when your program is ready for delivery, because that’s when undiscovered bugs have their worst consequences. Remove assertions only when the profiler tells you that you must.
Marc J. Rochkind
February 7, 1987
CompuServe 75765,1233
Copyright 1987 Advanced Programming Institute, Ltd. May be freely reproduced and distributed, but only in its entirety, with no editing whatsoever, except that misspellings may have the marking “[sic]” inserted after them, like thas [sic]. The author’s name and this Copyright notice must be included.