Latest Review: October 24, 1997
Latest Edit: $Date: 1998/03/10 19:34:33 $
Part of the 6 Cs of C Coding.
[ I copied this document up from my work. I'm not sure I fully agree with all of the following since my coding styles tend to migrate, but this is interesting and maybe a good starting point for others. We did this over a couple of days with all of our developers (about 10 at the time I'd guess). We all had to compromise somewhat but in general we were happy with the output. ]
The following are recommendations as to a proper coding style. The engineering department (at least the Pittsburgh segment) has reviewed this document at least twice. Although you may think that many of the following guidelines are unnecessary under certain circumstances, it is important to realize that a lot of code segments gets copied around between modules what you are writing often propagates into circumstance which are very different.
General Global Variable Variable Naming Variable Scope Variable Declarations Macros Include Files Functions General Coding Porting Issues Threading Commenting / Whitespace
It is recommended that modules contain a ChangeLog file. ChangeLog files are module-level change histories where a developer can annotate bug fixes, feature additions, and other changes to the module. When a major release is done, this file can be used to provide information of the changes after they have been reduced into layman's terms. In emacs, you can use the M-x add-change-log-entry command. For those using other editors, an entry looks like the following example, with later changes closer to the front of the file.
Mon Oct 6 22:22:20 1997 Gray Watson <gray@bobcat> * Releasing cqd version 1.6.5. * Fixed problems with field search and the NOT operator. Boy is this getting complicated. Wed Oct 1 23:31:37 1997 Gray Watson <gray@bobcat> * Improved the catalog status reports immensely.
Globals
- Severely limit usage of global variables.
- All global variables in a module should be defined as static unless they are to be global to the program.
- All global variables should be placed near the top of the source file before any function definitions and code for easy location. They should not be hidden in the middle of the code. This also includes #defines although they can be moved to an include file.
- If you wish to group a set of functions together with their global variable(s), it is recommended that the code be moved out to a separate module.
- All global variables and functions should have a module_ prefix. This means that we will not have function or variable name clashing when we integrate a number of modules into a large program. So the exported foo function in the pursuit module would be named pursuit_foo().
- Global variables, #defines , and static variables should each be grouped together with others of its kind at the top of the file. Consider having the order be the following:
/* normal includes */ #include <stdio.h> /* sys/, net/, or other includes under /usr/include */ #include <sys/types.h> #include <net/arpa.h> /* lycos includes in other libraries */ #include "argv.h" /* includes in this module */ #include "foo.h" /* * Now defines and any typedefs */ #define FOO_BIG 1024 #define FOO_SMALL 1024 /* foo item counter typedef */ typedef struct foo_st { char *item_name; int item_c; } foo_t; /* * Next exported variables. */ int foo_important_setting = FOO_BIG; /* seen externally */ /* * Next global variables that are static to this file. */ static long big_number = 0L; /* global, non-exported var */ /* * Then come the procedures starting with the statics. */ static int foo_startup(void) { ... } /* * Then finally the exported functions. */ int foo_alloc(void) { ... }- Consider identifying global variables with a g_ prefix so they can be easily distinguished among other variables. This improves the readability of code by immediately flagging those variables which if changed can impact the entire file.
Naming
- Variable names should be lowercase. Uppercase is discouraged. This helps with identification of items in the source and helps distinguish them from macros and other #defines.
Consider naming variables that count items by the name of item they are counting followed by the extension _c. So a line counter would be line_c. A element counter would be element_c.
Consider also the extension _i as identifying an array or for-loop index.
Consider the extension _n as identifying a variable holding the number of something. So the number of elements would be element_n.
Consider the extension _p as a pointer variable. So a line pointer should be line_p. A pointer to an element, element_p. One caveat to this is that the (char *) "string" type would not have a _p. So a character buffer might be named buf however a pointer that walks across this buffer might still be buf_p.
Consider naming booleans (variables holding true or false values) as the name followed by the extension _b. So a verbose flag should be verbose_b.
Consider naming file descriptor variables with the _fd extension, "FILE *" file-pointer variable with _fp, and socket descriptor variables with a _sd.
Combinations of extensions are allowed. A pointer to a pointer could then have a _pp extension.
- Consider having all types having a _t extension and all structures having _st.
- Consider having all enumerated types having the _e extension and should be used instead of defines when possible. A good debugger (gdb) can display the enumerated value instead of just a number.
- Avoid using one letter variable names. i, j, k and such variable names say nothing to someone reading the code. Exceptions to this rule are small loops with a few statements. At code review sessions, harassment will follow if other usages are discovered.
- If you use temporary variables inside of a #define macro block, be sure that they begin with an _ underscore character so they do not clash with local variables.
- Never use the register variable construct. Modern compilers are much more intelligent about register allocation than humans.
Scope
Limit the scope of variables as much as possible. Use local variables on the stack whenever possible instead of global. Although it can be a bad thing, passing arguments around to functions is much better style then passing information into a routine as a global variable.
In situations where you have a large buffer on the stack and are worried about problems with threads overflowing their stack space, use the heap for the allocation instead.
- Keep structures internal to a module private by defining them to be void * externally. This preserves and enforces the black-box nature of a module. This is best shown by examining libtable. The library exports a table_t * which is defined as a void *.
/* * In table.h */ #ifdef TABLE_MAIN #include "table_loc.h" /* where table_t is fully defined */ #else typedef void table_t; /* generic table type */ #endif ... /* so table_alloc returns a pointer to this item or a void * */ extern table_t *table_alloc(const unsigned int bucket_n, int *error_p);The table c-files routines all #define TABLE_MAIN which will cause table_loc.h to be included here to fully define table_t internally -- complete with all of the fields. So externally the functions using the table library only see an anonymous type and cannot fool with the fields inside of the table.
One problem remains which is the fact that when you are debugging a program and are in a routine external to libtable, you cannot see into the structure to make sure it has not been overwritten. This problem is solved by the following.
/* * In table_loc.h */ /* main table structure */ typedef struct table_st { unsigned int ta_magic; /* magic number */ unsigned int ta_flags; /* table's flags defined in table.h */ ... } table_t; /* external table structure type for debuggers */ typedef table_t table_ext_t;In a debugger, you can use the table_ext_t type to dump a table. You then can do a print (table_ext_t *)table_p .
Declarations
- All global and local variables should be initialized. Assumptions about auto-initialization should never be made.
- Consider putting each variable defining on its own line. This applies both to global variables at the top of a file and local variables in a function. This is especially important if the variable name is large and/or it is difficult to read because too many variables are on a line. The same applied to function arguments which should be easy to parse.
- Only constants can be used in initialization sections of variable declarations. No complex variable initialization such as:
time_t now = time(NULL) + 20; int x = 0; /* this is okay */ ...This should instead be:
time_t now; int x = 0; now = time(NULL) + 20;
#define FOO1(x) (x * 4) /* BAD: no () around argument x */ ... y = FOO1(1 + 2); /* this would not result in 12 because 1 + 2 * 4 = 9 */ ... #define FOO2(x) ((x) * 4) /* CORRECT: x inside macro within () */ ... y = FOO1(1 + 2); /* this would result in 12 */ ...
You can see that it is also important to surround the entire results from a macro with () (or maybe with a do while(0)) otherwise the same problem can happen.
#define foo(x) do { \ printf("Value = %d\n", (x)); \ (x) = (x) + 1; \ } while(0) /* notice no ; */
Without the do while, you would have problems using the macro in if () else statements. The while(0) gets compiled out by a good optimizing compiler so it does not waste cycles.
#define LOGIN_PROMPT "Hello there. Enter your name: "
You then can have the compiler calculate the length with the following. The -1 is because sizeof picks up the \0 character as well.
#define LOGIN_PROMPT_LENGTH (sizeof(LOGIN_PROMPT) - 1)
#ifndef __FILE_H__ #define __FILE_H__
At the end it would have:
You should replace the FILE_H with the name of your include file.#endif /* ! __FILE_H__ */
Consider using the fillproto perl script to generate your function prototypes for you. It is fairly simple. If you have a file foo.c that you want to put the prototypes in foo.h then at the end of the file before the #endif (explained above), add the following lines which must contain at least 5 less-than (<) signs.
/*<<<<<<<<<<*/ /*<<<<<<<<<<*/
These lines will then get substituted with the prototypes including any comments immediately proceeding your functions or non-static global variables.
/*<<<<<<<<<< The below prototypes are auto-generated by fillproto */ /* * table allocation function */ extern table_t *table_alloc(const int bucket_n); /*<<<<<<<<<< This is end of the auto-generated output from fillproto. */
if (y > 45 && (x = function()) != 0) { ... }
Simple single statements of such type can be appropriate since it produces more compact code. However complex multiple test statements should be avoided.
int foo(char *string, int size) { /* string and size are being modified here */ for (; size > 0; size--, string++) { *string = toupper(*string); } }
It can better be written as:
int foo(const char *string, const int size) { char *str_p; for (str_p = string; str_p < string + size; str_p++) { *str_p = toupper(*str_p); } }
if (! function()) { ... }
It should be replaced with instead:
if (function() == 0) { ... }
This is especially true for strcmp:
if (strcmp(...) != 0) { ... }
It is not that difficult to type and is much more readable. Also:
if (function()) { ... }
It should be replaced by:
if (function() != 0) { ... }
for (ele_c = 0; ele_c < 100; ele_c++) { ... } if (ele_c == 100) { ... }
If the purpose of the above test to handle the cases when the program did not break out the loop, then the conditions where elec > 100 should be considered as well -- either as a valid value or an error. This helps catch errors in cases where something in the loop modifies the ele_c variable. Remember that since most code is copied from one part to another, although this may not be the case in this example, it may in the future.
if (function() == -1) { ... }
test for more errors by replacing the test with:
if (function() < 0) { ... }
/* * table_t *table_alloc (type with static or not and name of the function) * * DESCRIPTION: * * Allocate a new table structure. (description of what is does) * * RETURNS: * * Success: A pointer to the new table structure. (what it returns normally) * * Failure: NULL. (what it returns when there is an error) * * ARGUMENTS: (description of each of the arguments) * * bucket_n - Number of buckets for the hash table. * * error_p - Pointer to an integer which upon return will contain a * table error code. */ extern table_t *table_alloc(const unsigned int bucket_n, int *error_p);
Free Spam Protection Android ORM Simple Java Zip JMX using HTTP Great Eggnog Recipe