Plasma Development C Style Guide

Gert Meulyzer
<gert@plasmalang.org>
version 4, 29 Oct 2016
Table of Contents

General Project Style Guide

  • Any user-visible changes such as new compiler options or new features should be documented. Any major new features should be documented to be included with release notes. This is especially important for anything that might cause anyone’s existing code to break.

These coding guidelines are presented in the briefest manner possible and therefore do not include rationales. Because the coding standard has been kept deliberately brief, there are some items missing that would be included in a more comprehensive standard. For more on commonsense C programming, consult the Indian Hill C coding standard or the comp.lang.c FAQ.

File organization

Modules and interfaces

  • We impose a discipline on C to allow us to emulate (poorly) the modules of languages such as Ada and Modula-3.

  • Every .c file has a corresponding .h file with the same base name. For example, list.c and list.h. The exception is pz_run_*.c which all share pz_run.h as alternative implementations.

  • We consider the .c file to be the module’s implementation and the .h file to be the module’s interface. We’ll just use the terms ‘source file’ and ‘header’.

  • All items exported from a source file must be declared in the header. These items include functions, variables, #defines, typedefs, enums, structs, and so on. In short, an item is anything that doesn’t allocate storage. Qualify each variable declaration with the ‘extern’ keyword, otherwise storage for the variable will be allocated in every source file that includes the header containing the variable definition.

  • We import a module by including its header. Never give extern declarations for imported functions in source files. Always include the header of the module instead.

  • Each header must #include any other headers on which it depends. Hence it’s imperative every header be protected against multiple inclusion. Also, take care to avoid circular dependencies.

  • Always include system headers using the angle brackets syntax, rather than double quotes. That is #include <stdio.h>. Plasma-specific headers should be included using the double quotes syntax. That is #include "pz_run.h" Do not put root-relative or ‘..’-relative directories in #includes.

Organization within a file

Source files

Items in source files should in general be in this order:

  1. Prologue comment describing the module.

  2. #includes of system headers (such as stdio.h and unistd.h)

  3. #includes of headers specific to this project.

  4. Any local #defines.

  5. Definitions of any local (that is, file-static) global variables.

  6. Prototypes for any local (that is, file-static) functions.

  7. Definitions of functions.

Within each section, items should generally be listed in top-down order, not bottom-up. That is, if foo() calls bar(), then the definition of foo() should precede the definition of bar().

Header files

Items in headers should in general be in this order: typedefs, structs, unions, enums, extern variable declarations, function prototypes then #defines However, it is probably more important to group items which are conceptually related than to follow this order strictly. Also note that #defines which define configuration macros used for conditional compilation or which define constants that are used for array sizes will need to come before the code that uses them. But in general configuration macros should be isolated in separate files.

Every header should be protected against multiple inclusion using the following idiom:

#ifndef MODULE_H
#define MODULE_H

/* body of module.h */

#endif  /* ! MODULE_H */

Comments

What should be commented

Functions

Each function should have a one-line description of what it does. Additionally, both the inputs and outputs (including pass-by-pointer) should be described. Any side-effects not passing through the explicit inputs and outputs should be described. If any memory is allocated, you should describe who is responsible for deallocation. If memory can change upon successive invocations (such as function-static data), mention it. If memory should not be deallocated by anyone (such as constant string literals), mention this.

Macros

Each non-trivial macro should be documented just as for functions (see above). It is also a good idea to document the types of macro arguments and return values, e.g. by including a function declaration in a comment.

Headers

Such function comments should be present in header files for each function exported from a source file. Ideally, a client of the module should not have to look at the implementation, only the interface. In C terminology, the header should suffice for working out how an exported function works.

Source files

Every source file should have a prologue comment which includes:

Copyright notice.
License info (e.g. GPL or LGPL).
Short description of the purpose of the module.
Any design information or other details required to understand and maintain the module.

Global variables

Any global variable should be excruciatingly documented. This is especially true when globals are exported from a module. In general, there are very few circumstances that justify use of a global.

Comment style

Use comments of this form:

/*
 * Here is a comment.
 * And here's some more comment.
 */

For annotations to a single line of code:

i += 3; /* Here's a comment about this line of code. */

Guidelines for comments

Revisits

Any code that needs to be revisited because it is a temporary hack (or some other expediency) must have a comment of the form: /* * XXX: <reason for revisit> */ The <reason for revisit> should explain the problem in a way that can be understood by developers other than the author of the comment.

Comments on preprocessor statements

The #ifdef constructs should be commented like so if they extend for more than a few lines of code:

#ifdef SOME_VAR
        /*...*/
#else   /* ! SOME_VAR */
        /*...*/
#endif  /* ! SOME_VAR */

Similarly for #ifndef. Use the GNU convention of comments that indicate whether the variable is true in the #if and #else parts of an #ifdef or #ifndef. For instance:

#ifdef SOME_VAR
#endif /* SOME_VAR */

#ifdef SOME_VAR
        /*...*/
#else /* ! SOME_VAR */
        /*...*/
#endif /* ! SOME_VAR */

#ifndef SOME_VAR
        /*...*/
#else   /* SOME_VAR */
        /*...*/
#endif  /* SOME_VAR */

Declarations

Pointer declarations

Attach the pointer qualifier to the variable name.

char    *str1, *str2;

Static and extern declarations

Limit module exports to the absolute essentials. Make as much static (that is, local) as possible since this keeps interfaces to modules simpler.

Typedefs

Use typedefs to make code self-documenting. They are especially useful on structs, unions, and enums.

Naming conventions

Functions, function-like macros, and variables

Use all lowercase with underscores to separate words. For instance, soul_machine.

Enumeration constants, #define constants, and non-function-like macros

Use all uppercase with underscores to separate words. For instance, MAX_HEADROOM.

Typedefs

Use first letter uppercase for each word, other letters lowercase and underscores to separate words. For instance, Directory_Entry.

Structs and unions

If something is both a struct and a typedef, the name for the struct should be formed by appending ‘_Struct’ to the typedef name:

typedef struct Directory_Entry_Struct {
        ...
} DirectoryEntry;

For unions, append ‘_Union’ to the typedef name.

Syntax and layout

Minutiae

Use 4 spaces to a tab. No line should be longer than 77 characters. If a statement is too long, continue it on the next line indented two levels deeper. If the statement extends over more than two lines, then make sure the subsequent lines are indented to the same depth as the second line. For example:

here = is_a_really_long_statement_that_does_not_fit +
        on_one_line + in_fact_it_doesnt_even_fit +
        on_two_lines;
if (this_is_a_somewhat_long_conditional_test(
        in_the_condition_of_an +
        if_then))
{
    /*...*/
}

Statements

Use one statement per line. Here are example layout styles for the various syntactic constructs:

If statement

Use the "/* end if */" comment if the if statement is larger than a page.

/*
 * Curlies are placed in a K&R-ish manner.
 * And comments look like this.
 */
if (blah) {
        /*
     * Always use curlies, even when there's only
         * one statement in the block.
         */
} else {
        /* ... */
} /* end if */

/*
 * if the condition is so long that the open curly doesn't
 * fit on the same line as the `if', put it on a line of
 * its own
 */
if (a_very_long_condition() &&
    another_long_condition_that_forces_a_line_wrap())
{
    /* ... */
}

Functions

Function names are flush against the left margin. This makes it easier to grep for function definitions (as opposed to their invocations). In argument lists, put space after commas. And use the /* func */ comment when the function is longer than a page.

Unlike other code blocks, the open-curly for a function should be placed on a new line.

int
rhododendron(int a, float b, double c)
{
    /* ... */
} /* end rhododendron() */

Variables

Variable declarations shouldn’t be flush left, however.

int x = 0, y = 3, z;

int a[] = {
    1,2,3,4,5
};

Switches

switch (blah) {
    case BLAH1:
        /*...*/
        break;
    case BLAH2: {
        int i;

        /*...*/
        break;
    }
    default:
        /*...*/
        break;
} /* switch */

Structs, unions, and enums

struct Point {
    int tag;
    union cool {
        int     ival;
        double  dval;
    } cool;
};
enum Stuff {
    STUFF_A, STUFF_B /*...*/
};

Loops

while (stuff) {
    /*...*/
}

do {
    /*...*/
} while(stuff)

for (this; that; those) {
    /* Always use curlies, even if no body. */
}

/*
 * If no body, do this...
 */
while (stuff)
    {}
for (this; that; those)
    {}

Preprocessing

Nesting

Nested #ifdefs, #ifndefs and #ifs should be indented by two spaces for each level of nesting. For example:

#ifdef GUAVA
  #ifndef PAPAYA
  #else /* PAPAYA */
  #endif /* PAPAYA */
#else /* not GUAVA */
#endif /* not GUAVA */

Portability

Architecture specifics

Avoid relying on properties of a specific machine architecture unless necessary, and if necessary localise such dependencies. One solution is to have architecture-specific macros to hide access to machine-dependent code. Some machine-specific properties are: Size (in bits) of C built-in data types (short, int, long, float, double). Byte-order. Big- or little-endian (or other). Alignment requirements.

Operating system specifics

Operating system APIs differ from platform to platform. Although most support standard POSIX calls such as ‘read’, ‘write’ and ‘unlink’, you cannot rely on the presence of, for instance, System V shared memory, or BSD sockets. Adhere to POSIX-supported operating system calls whenever possible since they are widely supported, even by Windows and VMS.

The CFLAGS variable in the Makefile will request that modern versions of GCC fail to compile Plasma if it uses non-POSIX APIs. As we add more capabilities we may require more operating system features.

CFLAGS=-std=c99 -D_POSIX_C_SOURCE=200809L -Wall -Werror

When POSIX doesn’t provide the required functionality, ensure that the operating system specific calls are localized.

Compiler and C library specifics

We require a C99 compiler. However many compilers often provide non-standard extensions. Ensure that any use of compiler extensions is localised and protected by #ifdefs. Don’t rely on features whose behaviour is undefined according to the C99 standard. For that matter, don’t rely on C arcana even if they are defined. For instance, setjmp/longjmp and ANSI signals often have subtle differences in behaviour between platforms.

If you write threaded code, make sure any non-reentrant code is appropriately protected via mutual exclusion. The biggest cause of non-reentrant (non-thread-safe) code is function-static data. Note that some C library functions may be non-reentrant. This may or may not be documented in the man pages.

Environment specifics

This is one of the most important sections in the coding standard. Here we mention what other tools Plasma may depend on.

Tools required for Plasma

In order to build Plasma you need: * A POSIX (1-2008) system/environment. * A shell compatible with Bourne shell (sh) * GNU make * A C11 compiler * Mercury 14.01.1 or newer.

Documenting the tools

If further tools or libraries are required, you should add them to the above list. And similarly, if you eliminate dependence on a tool, remove it from the above list.

Coding specifics

  • Prefer enums to lists of #defines. Note that enums constants are of type int, hence if you want an enumeration of chars or shorts, then you must use lists of #defines.

  • Parameters to macros should be in parentheses.

#define STREQ(s1,s2)    (strcmp((s1),(s2)) == 0)

comments? see our contact page. Note: This coding standard based on the "Coding Standard for the Mercury Project".