2.2) C Programming Style The following is a brief list of "Sane" C programing conventions. If you follow them, C becomes a much "safer" language to use. In this class you must follow these conventions or your program may be given a grade of 0. You do not have to agree with this style, but you should consider why each item is specified. When doing so you might ask how JAVA inforces the structure suggested here. ------------------------------------------------------------------ C program organization. place the following parts of a C program in the order shown. includes of system header files #include includes of local header files #include "my_file.h" preprocessor "#defines" #define DEBUG declarations needed for system library files (SIC headers) external data definitions (usually only in file with main() ) int prog; external static data definitions static int file; external static function declarations (for static functions below) static int f(double x[]); external function definitions (if main() is in this file, put it first) int push(double x) { ... } external static function definitions (static functions) static int f(double x[]) { ... } Comments should be used between and in these sections in a consistent manner (comments are addressed later). For every library function used, include the indicated headed file(s). Only include "header" (.h) files. Always make your own header files end in ".h". Header files should only contain: preprocessor directives: #include, #define, #undef, #ifdef ... C declarations (no C definitions). All function declarations (prototypes) should be "fully specified". That is: All argument fields should have their types specified and a dummy variable name corresponding to the name used in the function definition should be used. All functions should have specified return types. Always declare/define functions without arguments as (void), not (). Avoid defining your own "varags" style functions (like printf(3C)). All functions you define should have a fixed number of arguments with fixed specified types. If you need to pass a variable number of items to a function, pass a pointer to a list, viz int main(int argc, char *argv[]) { ... . Most functions should be in their own files. A function "f()" should in a file "f.c", and a declaration (prototype) for f() should be in a file "f.h". Exceptions are "main()" and functions that form a "module". An exception to the last rule is the case when you have several related functions, that you want in separate files (such as mathematical functions sin(), cos(), tan() ... in files sin.c, cos.c, tan.c ... ). In that case, a single header file may be used (math.h), containing declarations for all the functions. Only put two global functions in the same file if they share access to the same private ("static") data or function. This extends to more than two functions and represents a "module" in C. When there are more than two global functions in a file (such as a stack module in stack.c with init(), push() and pop() ), declarations (prototypes) for the global functions should be in a single header file (stack.h). Any time a function, f(), defined in one file, f.c, is used in another file, prog.c, the function's header file, f.h , must be included in both files, f.c and prog.c . Check return values for any system call or function call that might fail on error conditions. Usually, place these checks in an if, such as: if((fd=open("file",O_RDONLY)) < 0 ) { // error code } Sometimes such checks can appear in a while() or for(;;). For functions you write, that might fail, try to use return values that can be tested as part of an if as above. The "usual" error returns for functions that return: positive or non-negitive integers is -1; chars is -1; pointers is ((pointer_type)0 ) If your function does not return a value, other than an error return value, let it return -1 for error and 0 for no error. Use a utility, such as make, to maintain consistency across modules and automate/record the steps in building, testing, and installing a system. Not "exactly" C programming style, but it is C development style. Macro variables should be all upper case. Function, Structure, Pointer and Variable names should start with a lower case letter. Do not use register variables (except for some compilers for some microcontrollers). Use pointers "wisely". The use of "void *" pointers and "casting" should only be used when really justified. Only use global variables when they are really needed. Avoid "hiding" variables defined in an outer scope. Limit the use of the goto (never use it). Use break/continue when early termination of a loop from inside the loop is required. Always include a "default" in every "switch()" statement, and try to use a "break" with every "case". Only use the while() and for(;;) constructs for looping (avoid the do-while loop when). Never return from a function by hitting the last "}", instead always use an explicit return or exit(). Always use an exit() when returning from main(), do not use a return from main(). Always use exit(0); in a program that ran "correctly" and use a non-zero exit status when a program did not run "correctly". Prevent the "shortcomings in C" (section 1.1) from affecting the results of your program. Compiler options to do stricter checking then the C standard requires should be used, such as not allowing any variables or functions that are only implicitly defined. (eg for gcc at "least" CFLAGS = -ansi -pedantic -Wall) Avoid using any code, or library functions, that copies a source of "unverified" size, without restricting the amount copied to the size of the destination. (*) As an example use strncp() rather than strcp(), unless you are 100% sure that the source string will fit in the destination. Any string input from an outside source can never be assumed to have a "verified" size, even if it is "supposed" to have a limit. Always verify the "validity" of external input. Follow the the rules of the command syntax standard (getopt(3c)), for flag options and arguements. Try to keep your use of flags and command line arguments very simple. If you can logically write your program to use stdin and stdout so that it can act as a filter, do so. Write Error Messages and Debugging to "stderr", not "stdout". Use "#ifdef DEBUG / #endif" blocks to support debugging code. Place debugging code at the top of functions, showing values of the parameters on entry. Label your debugging output clearly. Comments should include at least the following: First comment at top of file: File Name Date \ Version Programmer Name(s) Copyright Introduction Comment, near top of file, which tells what the program / function does. Global Variable Comments. each global variable defined should have a short comment. Function Comments, at the top of each function. Purpose of the function A short comment on each: global variable that is used in the function argument passed to the function local (static and automatic) variable These comments should be enough to use the function or program after reading them, without reading the code. A separate "man page" for the function should be written for functions that are part of a user "library" or programs that will be often used by others. All comments should follow a consistent style and should not interfere with reading the code. /* */ vs // Formatting: Be consistent. Do not place multiple statements on the same line. while() OR while() { NOT while() ; a=2; NOT while() ; ; ; a=2; a=2; } i = 6; NOT i=6; j=7; j = 7; Indent "blocks", so that the top and bottom of a block visible while(...) { OR while(...) XXXX { } XXXX } Space, both vertical and horizontal, is "valuable", do not waste it. Large blocks of whitespace are not useful. Avoid very long lines and very long functions. Exercise: Each of the the rules in the C Programming Guidelines above may be classified into addressing: (C) C shortcomings (not including explicit module support), (M) Module development, (S) Style for consistency and readability. and (P) Good programming practice For each rule, indicate which of these is being addressed. Exercise: Find some other C Programming Guidelines and compare. Exercise: Try to use indent(1) to format yout code. Exercise: For your compiler, find any options that will provide stricter checking and warnings then the C Standard requires. Exercise: Why might a program compiled with the -g option execute correctly, while when compiled without the -g option it does not? Exercise: What is the difference between "porting C code" from one system to another and "writing portable C code"? Exercise: The guidelines above were not explicitly intended to address writing portable code. What might be added to address "writing portable C code"? Exercise: What methods and tools are available to help in supporting portable C code? (eg gnu configure)? Exercise: Discuss testing and maintenance of C code based systems, relative to programming guidelines.