Skip to main content
Basic Programming In C
Basic Programming In C

C Programming Conventions

Info

This document is based on the coding conventions of Carnegie Mellon Institute of Technology, itself based on the NetBSD conventions and the C++ conventions of Todd Hoff. A configuration file for clang-format is available at the end to best respect these conventions.

This document lists recommendations for writing code in C (in particular for the Structured Programming and Advanced Programming modules). Of course these recommendations are valid regardless of the language used for coding (French/English). In the rest of the text, examples will be given in English.

Names

In general, it is important that names (of variables/functions/files) are explicit so that the reader has an idea of what is being manipulated. Therefore, avoid (except in very common cases such as i for a loop variable or x for an unknown) overly generic names.

Function Names

Functions (and procedures from an algorithmic point of view) represent a sequence of instructions that achieves an objective. It is generally common to name functions with a verb (for example: compute, print, get…). To make function names more explicit, multiple words in lowercase separated by _ will be used.

Example: print_array, compute_max, …

Some function prefixes are frequently used, for example is_ when expecting a boolean answer, get_ to obtain a value, or set_ to define a value.

Example: is_array_empty, get_maximum, set_state

Variable Names

Variables (and even types) represent things. It is common to name them with nouns (for example: maximum, index, speed…). The naming convention will be the same as for functions, i.e., lowercase with words separated by a _ character (except for constants which will be in uppercase).

Example: error_number, iteration_limit, average_value

In the case of variables representing physical quantities, variables will be suffixed with their unit.

Example: timeout_msecs, length_m

For pointers, variable names will be prefixed with p_:

Example: int *p_maximum

For global variables, the prefix g_ will be used (allows identifying the scope of the variable by its name):

Example: char * g_working_directory

Structure or Type Names

The following convention will be used: first letter of each word in uppercase (which serves as a separator), the rest in lowercase. For structures, care should also be taken to organize variables to minimize waste related to memory alignment. Example:


typedef struct
{
    unsigned char   id;
    unsigned char   birth_date;
    short int       phone_number;
    char            first_name[MAX_LENGTH];
    char            last_name[MAX_LENGTH];
}  Person;

typedef Person* PtrPerson;

Variables

Pointers

For pointers, the asterisk * must be placed close to the variable and not the type to avoid confusion when multiple variables are declared on the same line. For example:

char* name, letter; might suggest that both variables name and letter are pointers to characters, whereas only name is. The syntax char *name, letter; is more explicit and faster to parse.

Global Variables

Global variables should be avoided as much as possible. If in certain cases their use is unavoidable, care should be taken to respect the naming convention (i.e., prefix g_).

Constants

The use of the keyword const is preferred over #define since const provides type information, which allows the compiler to check type compatibility when using the constant. Constants should also be in uppercase with words separated by _. For example:


const int BUFFER_SIZE = 512;

Macros

For macros, the same naming convention as for constants is used (uppercase + _). Be generous with parentheses to avoid undesirable side effects. For example:


#define SUM(a,b)  ( (a) + (b) )

Enumerations

For the enumeration name, the type convention will be used (uppercase on the first letter of each word, the rest in lowercase). For each enumeration label, the constant convention will be used (uppercase with _ as separator).


enum GeometricSimplex
{
    VERTEX,
    LINE,
    TRIANGLE,
    TETRAHERDON
} ;

Blocks

Brace Placement

Either the K&R convention (for Kernighan & Richie) can be used, which places the opening brace on the same line as the condition:


if ( condition ) {
    instruction_1;
    instruction_2;
    instruction_3;
}

or the Allman convention (or ANSI C) which places the opening brace on the next line:


if ( condition )
{
    instruction_1;
    instruction_2;
    instruction_3;
}

Choose whichever you prefer and be consistent throughout all your programs! In all cases, the instructions in the block must be indented one tab to the right.

Use of Braces

In general, it is preferable to always use braces even when there is only one instruction in the block. This avoids errors if the code is modified and an instruction needs to be added to the block:


if ( condition )
    instruction_1;
    instruction_2;

always executes instruction_2, whereas


if ( condition )
{
    instruction_1;
    instruction_2;
}

only executes instruction_2 if the condition is true. To maintain code compactness, sometimes the block can be written on one line when there is only one instruction. For example:


if (condition) instruction_1;

Comments After Closing Braces

For cases where your blocks are long, it may be useful to add comments at the end of closing braces to better navigate your program. For example:


while ( condition_1 )
{
    if ( condition_2 )
    {
        // ...
    } /* condition_2 OK */
    else
    {
        // ...
    } /*condition_2 NOK */
} /* loop until condition_1 NOK */

Conditions

Try as much as possible to put the constant on the left side of the comparison operator in conditions (these are called yoda conditions). This allows the compiler to detect the confusion between the equality operator == and the assignment operator =. For example:

if ( BUFFER_SIZE == size )

And avoid as much as possible implying comparisons to 0 or NULL. For example:

if ( NULL == ptr_index) or if ( READ_ERROR == func(filename) )

is preferable to

if ( ptr_index ) or if ( func(filename) )

The fact that tests are explicit makes it easier to detect errors.

Abbreviated Form of if-then-else

It is possible to use the abbreviated form of if-then-else; however, care should be taken to make it clear by avoiding a then or else that is too long… the use of functions is therefore recommended. For example:

( condition ) ? func_1() : func_2()

goto, continue, break

Avoid using them as much as possible since their use produces programs whose execution is difficult to trace. In general, the same result can be obtained with a better-structured program.

Formatting

Parentheses

To easily distinguish functions from keywords, the following convention is used:

  • The opening parenthesis is inserted immediately after the function as: max(a, b);
  • A space is inserted between the opening parenthesis and the keyword as: while (condition)
  • Avoid parentheses for return when they are not necessary

One Variable/Instruction per Line

The idea is to facilitate code readability by avoiding multiplying variable declarations or multiple instructions on the same line. Thus avoid:


int *size, i, **matrix;
i = 0; load_matrix(matrix, size);

and instead write… and especially always initialize variables:


int * size   = NULL;
int   i      = 0;
int **matrix = NULL;
load_matrix (matrix, size);

Assignment in an Expression

In the same spirit as the previous paragraph and with the goal of readability and code maintenance, avoid assignments in an expression. For example:

d = (a = b + c) + r;

will be written as:

a = b + c;

d = a + r;

Miscellaneous

No Magic Numbers

Avoid putting values in programs that have no meaning. Use either variables or constants to clarify the meaning of each value. This allows for code evolvability and readability.

For example in the following code:


int i, j;
for ( i=0 ; i < 1000; i++)
{
    for ( j=0 ; j < 1000; j++ )
    {
        // ...
    }
}

it is impossible to know whether the 1000 in the first for corresponds to the same quantity as the one in the second for. So if the value of the stop condition of the first for needs to be changed, should it also be changed for the second for? In the current state it is impossible to know without carefully reading the code. With the following version, understanding the code and therefore its maintenance is much easier:


const int VECTOR_SIZE = 1000;
const int MAX_ITERATION = 1000;

int i, j;
for ( i=0 ; i < MAX_ITERATION; i++)
{
    for ( j=0 ; j < VECTOR_SIZE; j++ )
    {
        // ...
    }
}

Return Codes / System Calls

Except in special cases (for example for printf), test the return codes of system calls (fopen, malloc, realloc…) to better trace program execution and detect possible bugs or errors.

Using #if

If you want to have code that is only compiled under certain conditions, it is possible to use the pair #if COND and #end. If COND equals 1, the code will be compiled; otherwise it will be ignored by the compiler. Consider the following program:


#include <stdio.h>

int main()
{
#if DEBUG
    printf("DEBUG set to 1\n");
#end
    return 0;
}

Depending on how it is compiled, a different result is obtained:


jdequidt@weppes:~$ clang exemple_if.c -o exemple_if -Wall
jdequidt@weppes:~$ ./exemple_if
jdequidt@weppes:~$ clang exemple_if.c -o exemple_if -Wall -DDEBUG=1
jdequidt@weppes:~$ ./exemple_if
DEBUG set to 1
jdequidt@weppes:~$

Using this technique, it is very easy to comment out large sections of code using #if 0 and #end. For example:


#if 0
    // code that we do not want to compile
#end