No Title

C/C++ Programming Conventions Prof. Thomas Narten
Duke University CS DepartmentLast Update: September 15, 1996

Introduction

There are three major sections in this note: (1) program submission guidelines, (2) general programming standards, and (3) naming conventions for variables in source programs.

Program Submissions

  1. All programs, including single file submissions, must include a Makefile. The Makefile should contain the following targets:
    all
    : The all target builds your program from sources, leaving the executable in the file a.out. The all target should be the default target, executed when make is invoked with no arguments.
    clean
    : The clean target removes all files that can be rebuilt from sources, including .o files, a.out, or those generated by such programs as lex and yacc.
    lint
    : For projects constructed from C source files, the lint target runs lint on the source files of your program. A lint target is unneeded for C++ programs, or on systems where lint is unavailable. When gcc is used, ``gcc -fsyntax-only -Wall'' can be used in place of lint.

  2. Submit programs for grading by running the turnin command. Turnin takes the names of the files being submitted for its arguments. Your submission must include all files needed by make above.

General Programming Standards

The following guidelines improve the readability of source programs. (To simplify adhering to these guidelines, emacs elisp modes are available.)

  1. Begin each module with a comment of the following form.

    /*
     * <filename> -- <brief title and/or purpose>
     *
     * programmer -- <you>
     *
     * date -- <creation date>
     *
     * modification history
     *
     * <date>        <change made>
     *
     */

    The modification history section is used to record changes made to an otherwise stable module.gif

  2. Include a set of external comments that describe the program at a high level. There are two types of external comments:
    1. The typical user wants to know details of how to call the program, its options, formats of data, limitations, bugs, and special features. For course programs, part of this information is included in the assignment and need not be repeated. Emphasize both the negative aspects of your program (limitations, known bugs) and the positive aspects (extensions, special features). For significant programs, this information merits a manual page.
    2. A person responsible for maintaining or modifying your program will want an overview of the algorithm(s) used in the program; parts of this information included in a course assignment need not be repeated. Include anything that may be different from what other students in the class did, or that is not immediately obvious. One goal of this documentation is to give the reader an overview of how the program works, so that they can understand smaller pieces of it without having to understand each procedure. For example, you may want to list the major components of your program, explaining how control flows from one module to another.
    For programs contained in a single file, external comments appear at the beginning of the file. For multi-file programs, place them in an obvious place such as in the file ``main.c'' or ``README''.

    Finally, comments are only useful if they are concise and make sense. Assume that the reader knows a programming language and is an experienced programmer. Write comments in such a way that if you were to look at the program one year from now, you would be able to figure out what it does and how it works.

  3. C procedures can be difficult to locate when scanning a file. Flag the beginning of each procedure by a comment of the following form:

    /*
     * ================================================================
     * <name> -- <brief statement of action performed in 
     *           terms of input parameters and value returned.>
     * ================================================================
     */

    The procedure comment should not be more than two or three lines. If you feel you need more lines, your procedure is probably doing too much. Separate procedures by one blank line. Be sure to indicate return values (if any) along with side effects such as modified global variables or pointer parameters used for returning values.

  4. Use #includes and header (.h) files to hold data definitions used in multiple source files. Header files should contain only definitions and no declarations. Avoid including source code via #include; use separate compilation instead.
  5. Use #define (or the const keyword) to define any constant (numeric, character, and string) that has meaning apart from its literal value. Most numbers other than 0 or 1 fit into this category.

  6. Declare no more than one global variable per line. Append a brief comment that describes the variable.

  7. Avoid goto's and other constructs that make the flow of control hard to follow.

  8. Include a comment in null statements. For example,

    for (i = 0; sb[i] ~= ' '; i++)
        /* null */;

  9. Try to keep lines less than 80 columns wide (so they fit nicely on the CRT).

  10. Align all statements that appear at the same level. Use four spaces for each nested level of indentation.

  11. Put each statement on a line by itself. Exceptions are made for very short statements that belong logically together (for example, to swap the values of two variables using a temporary).

  12. Separate the declarations and statements in a compound statement by a blank line.

  13. Align a comment that describes a block of code with the code, preceded by a blank line. Where possible, put a comment that describes only one line at the end of that line, rather than on a line by itself. Use comments sparingly; especially block comments.

  14. Place the curly braces that delimit the body of a procedure in column one and on lines by themselves. For other uses of curly braces, place the opening brace on the line that begins the compound statement (for example, at the end of a for header), and place the closing brace on a line by itself, lined up with the first non-blank character on the line of the statement containing the matching opening brace. For example,

    if (c <= cMax) {
        GetToken (c);
        c++;
    }

  15. The else clause of a simple if statement lines up with the if, whereas the closing brace lines up with the if for multi-statement clauses. Some examples
    if (fCheck)
        /* single statement */
    else ...
    if (fCheck) {
        ...
    } else ...
    
    if (fCheck) {
        ...
    } else if (i > 0) ...
  16. Use single if statements with return, break, etc. statements rather than if-then-else clauses to filter out terminating cases.
    if (fCheck) {
        return(1); /* or break, exit, etc. */
    }
    /* main code */    
    ...
    is easier to follow than
    if (!fCheck) {
        /* main code */
    } else {
        return(1); /* or break, exit, etc. */
    }

  17. Surround major operators in a statement (operators with the lowest precedence) by one space. Put a blank after commas in argument lists.

  18. Don't reinvent the wheel-use functions and procedures from the C library whenever possible. Section 3 in the Unix documents contains many useful functions (for example, string manipulation routines).

  19. Use programming tools to make your job easier. If you're not familiar with them, it is worth learning make, GNU Emacs, and rcs. Familiarity with a debugger (e.g., gdb) is essential (e.g., just after a core dump).

  20. Check return values from system calls and library routines. It is tempting to omit the check when you are certain the call ``can't fail''. Check it anyway. If you don't want to clutter your code with if statements, put the return value into a variable and use assert(3) to check it.
  21. Use perror to print meaningful error messages after failed system calls. A statement such as
    fp = fopen(``/etc/passwd'', ``r'');
    if (fp == NULL) {
        perror(``/etc/passwd''); 
        return(-1);
    }
    indicates which file could not be opened (and why), in contrast to the following:
    fp = fopen(``/etc/passwd'', ``r'');
    if (fp == NULL) {
        fprintf(stderr, ``Can't open file\n'');    
        return(-1);
    }
  22. Avoid using global variables as procedure parameters. Global variables should be reserved for data that is used by so many procedures that they would appear as parameters in a majority of calls.

  23. Keep routines general through careful use of parameters, but avoid extremes. Five or more parameters may mean that you are doing half the routine's work before you've even called it.

  24. Use dynamic memory allocation where necessary, but let the compiler do the work whenever possible. For instance, use local variables in procedures rather than allocating and immediately freeing space used in only that procedure. Use free to return unneeded memory previously acquired via malloc.

Naming Conventions

This section describes the methods for creating names for procedures and variables.

Procedures

Where possible, name procedures by the (single) action they perform. In most cases, the name should be a verb or verb phrase. Capitalize words that appear in the name (for example, GetToken).

Variables

Variable names should be mnemonic. That is, the name of a variable should relate to some property of the values that variable can contain. We shall name variables by the abstract type of their values.

An abstract type is a set of values on which a collection of operations can be performed.

For example, a file descriptor is an abstract type. Values of type file descriptor refer to files and can be used in certain i/o operations. However, multiplication and division on file descriptors has little (or specialized) meaning.

Programs often deal with physical properties of objects, such as length, time, or mass. The units of each of these properties define an abstract type, such as inches, minutes, or grams. Relative quantities also define abstract types. For example, values that describe the position of a point relative to one origin are of a different abstract type than values that describe the same point relative to a different origin.

An abstract type is not tied to a hardware representation in the same way that an ``ordinary'' programming language type is, but instead allows more precise expression of the meaning of a variable.

For example, C and Pascal allow any integer variable to be used as an array index of any array. Yet, an array can usually be considered a mapping from a value in a specific domain (the index) to a value in a specific range (the contents of the indexed location). To index an array with a variable that is not of the domain type of the array is either an error or a programming technique that should be used sparingly.

In the convention we will use, an array is named according to the abstract types of its domain and range. Thus, it is easy to tell (1) if the array is being indexed with a value of the proper type, and (2) if the value of the array is being used in the proper context.

So, how are variable names constructed?

Each simple abstract type used in a program is given a unique tag, a two-letter abbreviated name.gif For example, the tag for a character is ch. The tag for a string is sb (for string block).

Compound types, such as arrays, are given names constructed from the types of their components. For example, since an array is a map, array names start with the tag mp, followed by the tag of the domain, followed by the tag of the range. Thus, an array that maps characters to strings is named mpchsb.

A record (struct in C) defines a new type, so it is given its own tag. For example, a structure for defining a student record might be called an sr and be defined as

struct sr {
    int id;        /* student id */
    char *sb;      /* student name */
};

Occasionally, two different variables of the same type appear within the same scope. If this happens, a qualifier is appended to one or both of the variables. A qualifier is a short name that further defines the use or purpose of the variable. The first character of a qualifier is capitalized.

For example, if a procedure is reading characters until a special end of file character is read, the characters may be read into a variable named ch and compared to the character in the variable named chEOF.

Compound Types

The naming convention provides type construction rules or schemas that show how to construct compound types from simple types. A large program typically has only a few (say 10-20) simple types. This ability to quickly and easily construct names of variables based on the types they contain is the real power of the convention.

Let X and Y denote arbitrary tags (two-character mnemonics for simple types). Each of the following schemas shows how to construct a new type from the given simple type.

Common Basic Types

Examples

Using this notation, the arguments to the main procedure of a C program, argc and argv, are called csb and rgsb.

Common X-Related Types

Reference

This document is based heavily on one created by John T. Korb, Department of Computer Science, Purdue University. Original reference: Charles Simonyi, Meta-Programming: A Software Production Technique, pp. 34-45, Xerox PARC technical report CSL-76-7.

About this document ...

This document was generated using the LaTeX2HTML translator Version 0.6.4 (Tues Aug 30 1994) Copyright © 1993, 1994, Nikos Drakos, Computer Based Learning Unit, University of Leeds.

The command line arguments were:
latex2html -split 0 main.tex.

The translation was initiated by Thomas Narten on Sun Sep 15 17:13:05 EDT 1996


Thomas Narten
Sun Sep 15 17:13:05 EDT 1996