C/C++ Style Guide

Programming style is one of the holy wars of computer programming. However, regardless of your tastes one thing is certain: when in Rome, do as the Romans do. That is, learning the accepted style of a programming language is just as important as learning the language itself.

It seems that every company or group has its own style guidelines (e.g., the linux kernel C guide, the Google C++ style guide, or the Mozilla coding style guide). Regardless, for consistency's sake, we'll stick to a set of common rules for our code. Correctly following the coding style will be worth about 10% of the total grade for each homework.

(This guide borrows heavily from the OCaml style guide for old iterations of CIS 500.)

Formatting

  1. 80 Character Column Limit: No line of code can have more than 80 columns. Using more than 80 characters per line causes your code to wrap around to the next line, which is devastating to the readability of your code.
  2. No Tab Characters: Do not use the tab character (0x09). Instead, use spaces to control indenting. Ensure that your editor of choice uses spaces when you insert a tab (i.e., soft tabs).
  3. Indent Two or Four Spaces: Most lines that indent code should only indent by two or four spaces more than the previous line of code. Pick one and be consistent.
  4. Line Breaks: Obviously the best way to stay within the 80 character limit imposed by the rule above is pressing the enter key every once in a while. Including empty lines should only be done between declarations (e.g., class definitions or field/method definitions within classes) and should be restricted to a single empty line at most.

    If a declaration or expression extends over 80 characters, indent to the opening delimeter of the declaration or expression. For example, the following uses line breaks and indention to break up long parameter lists and boolean expressions.

    bool LargeMethod(FirstVeryLargeParamterType t1,
                     SecondVeryLargeParameterType t2,
                     ShortThirdParameterType t3)
    {
        return (SomeBooleanFunction(t1, t2)    ||
                AnotherBooleanFunction(t2, t3) ||
                SomeBooleanFunction(t1, t3));
    }
    
  5. Use K&R or Allman style braces: K&R looks like this:
    namespace N {
        class C {
            void f() {
                int x;
            }
        };
    }

    Allman style braces push the opening brace onto its own line:

    namespace N
    {
        class C
        {
            void f()
            {
                int x;
            }
        };
    }
    

    Pick a style of braces and consistently use that style on any given homework or the project.

Files Layout

    If you're wondering what belongs in what file ("Where do my prototypes go?" or "Do all my #defines need to be in one file?"), here are the answers you seek. (Please note that everything said about .c files also applies to .cpp files once we start using C++.)

  1. File that contains main(): This file will in most cases be the shortest .c file. It shouldn't contain much other than a few #include statements and the main() function. And main() itself should be as short and easy to read as possible -- this will help immensely with debugging. If you've written a large chunk of code in main() that accomplishes a specific purpose (e.g., reading in data from a file, printing out a set of information, decoding a specific type of encryption, etc.) you should likely break it off into an appropriately named function.
  2. Accesory .c files: Accessory .c files contain the function definitions for the functions used in main(). You may have more than one .c file in a given program, especially as you break your functions down into groups (or as we starting using classes in C++). Accesory .c files should only have a #include for their matching .h file, although they may also include a .h file that contains program-wide information (e.g., constants that all accessory files will need access to; or definitions for structs used by multiple accessory files).
  3. Accesory .h files: Accesory .h files are often the shortest files. They should contain only function prototypes for their matching .c file, and any constants (#define) and declarations (e.g., structs, typedefs) needed.
    Or, in short... where do you put? ...

  1. main(): In a file containing only main() and a few #include statements
  2. function definitions: accessory.c files
  3. function prototypes: accessory.h files
  4. #define statements: accessory.h files
  5. struct definitions: accessory.h files
  6. typedefs: accessory.h files
  7. variable definitions: inside main() function (not globally)
  8. #include statements: as needed
  9. comment containing your name: at the top of every file, including .h files

Readability

  1. Aesthetics: This goes hand in hand with limiting columns to 80 characters -- strive to make your code aesthetically pleasing, and it will also be more readable. A good example is below; although both the left and right sides are the same exact variable declarations, the right side is much easier to read with a quick glance.
    int choice;                                  int   choice;
    int i = 0;                                   int   i       = 0;
    float pi = 3.14159;                          float pi      = 3.14159;
    float e = 2.71828;                           float e       = 2.71828;
    char *str1 = "hello world";                  char  *str1   = "hello world";
    char str2[] = "testing123";                  char   str2[] = "testing123";
    
  2. Spacing: Try to use the same rules as those that are used in CIS 110. For example, use a single space on either side of mathematical operators, so that it looks like x + y and not x+y, and only put spacing outside parentheses, not inside, so it should look like a + (b + c) and not a+(b + c) or a + ( b + c ).
  3. No "Magic Numbers": Using "magic numbers" (numbers directly in code) can make code more difficult to parse, lead to inadvertant errors, and is an enormous pain to deal with if every instance of the number needs to be changed. Instead, use #define or const to give the "magic number" a meaningful name, and use that in your code instead. (As a rule of thumb, "magic numbers" are any number other than 0 or 1, with the exception of certain special cases, like in printf formatting).
  4. No Global Variables: Global variables are variables that have global scope, and are accessible throughout the entirety of the program, without needing to be passed to functions. They are also more work than they are worth, and cause more problems than they fix, which is why we won't be using them. If a variable is a constant value, use a #define or const in a header instead. If not, use a local variable, and pass it to functions as needed. Although there are exceptions, they are few and far between.
  5. No Infinite Loops: Any loops you use in your code must have clear terminating conditions. This means no while(1) or for( ; ; ) loops, which require you to look through the loop's entire body for the necessary terminating condition and break; statement. Instead, you may have to initialize the loop variable or perform a priming read beforehand.

    For example, instead of using the following code:

    int n;
    while (1) {
        n = GetUserInput();
        if ( (n >= 0) && (n <= 100) ) {
            break;
        }
    }
    

    You can either initialize the loop variable:

    int n = -1;
    while ( (n < 0) || (n > 100) ) {
        n = GetUserInput();
    }
    

    Or perform a priming read:

    int n = GetUserInput();
    while ( (n < 0) || (n > 100) ) {
        n = GetUserInput();
    }
    
  6. No Break Statements: (Outside of those used in switch statements, obviously.) Along with not using while(1) loops, your code should not need to use break; statements to accomplish its goals. If you are having trouble avoiding a break statement in your code, post a private question on Piazza with the code in question.

Naming

  1. Use meaningful names: Variable names should describe what they are for. Distinguishing what a variable references is best done by following a particular naming convention (see suggestion below). Variable names should be words or combinations of words except in special cases where single letter variables are understood, e.g., i and j for loop variables.
  2. Case and naming conventions: The following table outlines the capitalization conventions we will use for names:
    Identifier Case Example
    Class Pascal Case AppDomain
    Method Pascal Case ToString
    Parameters, Locals, and Fields Camel Case myVariable
    Namespace Lower-case-underscores std::my_namespace
    Macros, #defines, and typedefs Angry MY_AWESOME_MACRO

    Definitions: Pascal case capitalizes the first letter of each word in the name. Camel case is the exact same as Pascal, except it leaves the first letter uncapitalized. Angry case is all-caps with words separate by underscores.

  3. Don't abuse typedef, #include, #define or using namespace in header files: Only typedef types that would be useful to someone who #includes the header file, because whatever you typedef in a header file carries over to any file that includes it. When it comes to using namespace, you usually shouldn't put this line in the header file for the same reason --- it automatically carries over to any file that #includes it, and could cause some name clashes.

Comments

  1. Comments go above the code they reference: For the sake of consistency (and to avoid issues with the 80 column limit), place comments above code. (If the comment is super-short, you can get away with placing it off to the side, but don't do it too often.) For example,
    /* This is a well-placed comment */
    #define AN_IMPORTANT_NUMBER 42
    
    // This is fine in C++, but not in C
    #define AN_IMPORTANT_NUMBER 42
    
    #define AN_IMPORTANT_NUMBER 42      // This is fine too
    
  2. versus other places, e.g.,
    result = CallFunction(variable1, stringVar2, Var_3)    // This is a badly placed comment off to the side -- it's way too long!
    
    #define AN_IMPORTANT_NUMBER 42
    /* This is a comment badly placed below */
    
  3. Take credit for your code: At the top of each file, there should be a comment that includes the file name, your name and Penn ID, and a short description of what the file contains/does. See hello_world.c from Lecture 1 for an example.
  4. Avoid over-commenting: Incredibly long comments are not very useful. Long comments should only appear at the top of a file, where you briefly explain the code, credit the author(s), and reference any sources that have more information about the algorithms/data structures if necessary. All other comments in the file should be as short as possible. After all, brevity is the soul of wit. Most often the best place for any comment is just before a function declaration. You should only need to comment inside the body of a function if it is extremely long or complicated --- variable naming should be enough in most cases.
  5. Avoid useless comments: Comments that merely repeat the code they reference or state the obvious are a travesty to programmers. Comments should state the invariants, the non-obvious, or any references that have more information about the code.

Verbosity

  1. Don't rewrite existing code: The C and C++ standard libraries have a number of functions and data structures --- use them (unless otherwise stated)!
  2. Boolean Zen: Remember that the type of the condition in an if-statement is bool. Avoid redundancy in your condition if you already have a value of type bool. For example
    if (foo == true) { /* ... */ }
    
    is redundant as foo must be a boolean variable. Therefore, we should rewrite this as
    if (foo) { /* ... */ }
    

In Summary

    The main points to remember when you're coding are as follows:

  1. Have a comment at the top of each file with at least your name and some basic information.
  2. Do not use: global variables, "magic numbers," or infinite/while(1) loops.
  3. Keep your code "clean" -- don't let lines wrap, and keep an eye out for the underlying aesthetics.
  4. Be consistent! Pick a style and stick to it, for braces, commenting, variable naming, indentation, spacing, and anything else you can think of.