Coding Standards

In professional software development settings, organizations typically have a set of coding standards, which are rules you are expected to follow regarding your style. Groups enforce these standards in a variety of ways, including the absolute rejection of any piece of code that deviates from this style. Here are coding standards appropriate for CS 261. They are mostly derived from the GNU standards for writing C, which are the standards often used in open source software. I have made some adjustments to more closely match my own C style, which is essentially K&R style with mandatory block braces (often called 1TBS) and opening function braces on new lines.

If you already have your own preferred coding style that is clean and consistent, you may use it in this class. Any of the standard indentation styles are permissible. If you choose to use an alternate style, make sure you also convert the starter code to maintain consistency.

Ultimately, you must submit code that is readable, clean, and consistent. I will deduct points from your project grade if your code is unreadable, cluttered, or inconsistent. Many programmers have legitimate disagreement about which coding standards are best, but there is nearly universal agreement that code should be readable, clean, and consistent. If you are unsure of what qualifies as "readable, clean, and consistent," please ask on Piazza or just use the coding standards specified here.

To help ensure consistent formatting, you can use the astyle utility to reformat your code before you submit it (or even while you are working). This utility should be installed in all the labs and the UUG Linux VM. Here is a set of command line parameters that will enforce 1TBS, 4-space indentation, and 100-character-max lines:

astyle -A10s4cxC100S <source-file>

In addition, you are required to adhere to the C99 standard. You may not use C11 or C++ features, and you may not use older C features that were deprecated by C99. By default, the provided Makefiles include compiler switches that enforce this standard--if you do not modify the makefile you shouldn't have to worry about violating the standard.

Much of the content in this coding standard is based on material originally provided by Dr. Michael Kirkpatrick.

  • Comments - Use them. Generally speaking, every 5-10 lines of code should have a brief one-line comment like shown below unless it is obvious what the code does. You should take particular care to document any "magic numbers," odd hacks (especially involving pointers), or anything else that might not be immediately clear to a reader. You may use either C-style /* */ comments or C++-style // comments, but make sure whatever you use is clean and readable.
  • Consistent file structure - All .c and .h files should look like this (note the use of #ifdef/#define tags to prevent multiple includes):

    /* foo.h */
    
    #ifndef __CS261_FOO__
    #define __CS261_FOO__
    
    /* other headers if needed */
    #include <stdbool.h>
    
    /* struct declarations needed by code other than foo.c */
    typedef struct foo {
        /* foo's internal stuff */
    } foo_t;
    
    /* function declarations needed by code other than foo.c */
    bool init_foo (foo_t *);
    
    #endif
                
    /* foo.c */
    
    /* includes needed only by code in THIS file */
    #include <stdio.h>
    #include <stdlib.h>
    
    /* struct declarations needed only by code in THIS file */
    typedef struct foo {
        /* foo's internal stuff */
    } my_foo_struct_t;
    
    /* prototypes for functions needed only by code in THIS file */
    void print_foo (my_foo_struct_t *);
    
    /* global variables */
    my_foo_struct_t master_foo;
    
    /* function definitions */
    
    bool init_foo (foo_t *new_foo)
    {
        /* do stuff */
    }
    
    void print_foo (my_foo_struct_t *foo)
    {
        /* do stuff */
    }
                

  • Indentation and alignment - Four spaces with curly braces for all blocks, and only on the next line for function. Examples:

    Do this: Don't do this
    int main (int argc, char **argv)
    {
        /* code here */
        return EXIT_SUCCESS;
    }
                    
    int main (int argc, char **argv) {
        /* code here */
        return EXIT_SUCCESS;
    }
                    
    for (i = 0; i < 10; i++) {
        printf ("%d\n", i);
    }
                    
    for (i = 0; i < 10; i++)
        printf ("%d\n", i);
                    
    for (i = 0; i < 10; i++) {
        printf ("%d\n", i);
    }
                    
    for (i = 0; i < 10; i++) {
            printf ("%d\n", i);
    }
                    
    if (i < 10) {
        printf ("%d\n", i);
    } else {
        printf ("Nope\n");
    }
                    
    if (i < 10)
        printf ("%d\n", i);
    else {
        printf ("Nope\n");
    }
                    
    switch (input) {
        case 1:
            printf ("1");
            break;
        default:
            printf ("2");
    }
                    
    switch (input) {
    case 1:
        printf ("1");
        break;
    default:
        printf ("2");
    }
                    

  • Naming conventions - When naming functions and variables, use all lower-case and use an underscore ('_') to separate words. Pick meaningful names and types, but keep simple things simple.

    Do this: Don't do this
    size_t bytes_read;
    char *message_read;
    for (char c = 'A'; c <= 'Z'; c++)
    int b;
    char *MyIncomingMessage; for (char current_character = 'A'; current_character <= 'Z'; current_character++)

  • No long lines - All lines of code should be no longer than 80 characters wide. Long lines of code should be broken and wrapped to the next line. The continued line should be indented in a natural alignment:

    Do this:
    some_variable = a_really_long_variable_name + another_variable +
                    yet_another_really_long_name
                
    int calculate_sum (int first_value, long second_value, int third_value,
                       unsigned int last_value)
    {
      /* ... */
    }
                

  • No long functions - Restrict functions to two screens (about 100 lines of code), and never have more than 3 levels of indentation. Code that is longer than this or uses more levels of indentation is typically too complex to maintain. For instance, you should never do this:

    Don't do this
    if (...) {
        while (...) {
            if (...) {
                if (...) {
                    for (...) {
                    

  • Use assertions for invariants - If there is a condition that should never be false (i.e., very bad, unpredictable behavior will occur), include an assert() in your code in an appropriate place. (Don't forget to #include <assert.h>!) Example:

    Do this
    void calculate_age (int birth_year)
    {
        /* birth_year should never be negative, otherwise strange things will happen */
        assert (birth_year > 0);
        return current_year() - birth_year;
    }
                    

  • Always initialize local variables - If you do not provide an explicit initial value for local variables, the variable will be created with an unpredictable, random value. This could cause very unexpected behavior, which may be different every time you run the program. If you don't know what to set it to, 0, false, or NULL tend to be good options. You might also consider using calloc instead of malloc to initialize dynamic memory to zero, but be aware of the performance impact of this change.
  • Safe pointer practices - After calling free() to free a pointer, immediate follow up by setting the pointer to NULL. After calling malloc() to get dynamically allocated data, validate the allocation was successful by checking that the pointer is not equal to NULL.
  • Safe input and output - Some functions in the C standard library are now considered to be "unsafe" because they are vulnerable to buffer overruns. For the purposes of this class, you may not use any of the unsafe functions. See the function quick reference for a list of unsafe functions and their alternatives.

Recommended guidelines

In addition to the requirements above, the following practices are used in the base source code distributions. Following these guidelines would help your code to match the existing style and make your code more readable.

  • When using malloc() to dynamically allocate an array, use sizeof() instead of making assumptions about data sizes:

    Do this: Don't do this
    size_t num_entries = 5;
    uint16_t *data =
        malloc (sizeof (uin16_t) * num_entries);
                
    uint16_t *data = malloc (10);
                

  • Short-circuit functions when checking for errors rather than using if-else blocks. This makes the error conditions explicit and keeps error-handling code close to where the error checking occurs.

    Do this: Don't do this
    if (value < 0) {
        fprintf(stderr, "bad value\n");
        return false;
    }
    
    /* lots of code here */
                
    if (value >= 0) {
    
        /* lots of code here */
    
    } else {
        fprintf(stderr, "bad value\n");
        return 1;
    }
                

  • Avoid global variables. Global variables should be thought of as a last resort, as the values can change from one function call to the next.
  • Use #define for constants as appropriate (and particularly avoid "magic numbers" that appear without explanation). The name of the constant should be in all upper-case:

    Do this: Don't do this
    #define QUESTION_COUNT 5
    for (int qid = 0; qid < QUESTION_COUNT; qid++)
    {
        /* stuff here */
    }
                
    for (int i = 0; i < 5; i++)
    {
        /* stuff here */
    }
                

Text Editor Setup

You may use any text editor, although on stu I recommend using nano, vim, or emacs. If you choose to use the nano editor, I recommend the following command-line options to help you stick to the coding standards:

nano -AEOSim -T 4 -r 80

To prevent having to type all of that every time, you can put the following command in your ~/.bashrc file (and then use "nnano" to launch the editor):

alias nnano='nano -AEOSim -T 4 -r 80'

Remember that makefiles do require tabs, so if you are editing a makefile you'll want to use the original version.

If you choose to use the vim editor, I recommend putting the following in your ~/.vimrc file:

" use four spaces for each step of (auto)indent.
set shiftwidth=4

" hitting <Tab> will insert four spaces instead.
set tabstop=4
set softtabstop=4

" round indent to multiple of shiftwidth.
set shiftround

" use spaces instead of tabs to insert a tab.
set expandtab

" copy indent from current line when starting a new line
" (also deletes indents if nothing else is written on the line)
set autoindent

" wrap lines at 80 characters
set textwidth=80

" exception: use tabs in Makefiles
autocmd FileType make set noexpandtab shiftwidth=4 softtabstop=0

If you wish to use the emacs editor you are on your own, and may Saint IGNUcious have mercy on your soul.