Conventions de Programmation en C

Ce document se base sur les conventions de codage de Carnegie Mellon Institute of Technology, lui même basé sur les conventions de NetBSD et les conventions C++ de Todd Hoff. Un fichier de configuration pour clang-format est disponible à la fin pour respecter le mieux ces conventions.

Ce document liste les recommandations pour l’écriture de code en C (en particulier pour les modules de Programmation Structurée et Programmation Avancée). Bien entendu ces recommandations sont valides quelle que soit la langue utilisée pour coder (français / anglais). Dans la suite du texte, les exemples seront donnés en anglais.

Noms

De manière générale, il est important que les noms (de variables / de fonctions / de fichiers) soient explicites pour que le lecteur ait une idée de ce qui est manipulé. Ainsi on évitera (hors cas très courants comme i pour une variable de boucle ou x une inconnue) des noms trop génériques.

Noms de fonctions

Les fonctions (et actions d’un point de vue algorithmique) représentent une séquence d’instructions qui réalise un objectif. Il est en général courant de nommer les fonctions par un verbe (par exemple: compute, print, get…). Pour que les noms de fonctions soient plus explicites, on utilisera plusieurs mots en minuscules et séparés par des _.

Exemple: print_array, compute_max, …

Certains préfixes de fonctions sont fréquemment utilisés par exemple is_ lorsque l’on attend une réponse booléenne, get_ pour obtenir une valeur ou set_ pour définir une valeur.

Exemple: is_array_empty, get_maximum, set_state

Noms de variables

Les variables (et même les types) représentes des choses. Il est courant de les nommer avec des noms (par exemple: maximum, index, speed…). La convention de hommage sera la même que pour les fonctions, i.e. en minuscules et les mots séparés par un caractère _ (sauf pour les constantes qui seront en majuscules).

Exemple: error_number, iteration_limit, average_value

Dans le cas de variables qui représentent des grandeurs physiques on suffixera les variables par leur unité.

Exemple: timeout_msecs, length_m

Pour les pointeurs, on préfixera les noms des variables par p_:

Exemple: int *p_maximum

Pour les variables globales, on utiliser la préfixe g_ (permet d’identifier la portée de la variable avec son nom):

Exemple: char * g_working_directory

Noms des structures ou types

On utilisera la convention suivante: première lettre de chaque mot en majuscule (qui fait office de séparateur) le reste en minuscule. Pour les structures, on pourra également prendre soin à organiser les variables de manière à minimiser le gaspillage lié à l’alignement mémoire. Exemple:

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

Pointeurs

Pour les pointeurs, il faut placer l’étoile * proche de la variable et non du type pour éviter les confusions quand plusieurs variables sont déclarées sur une même ligne. Par exemple:

char* name, letter; peut laisser supposer que les deux variables name et letter sont des pointeurs sur des caractères alors que seul name l’est. La syntaxe char *name, letter; est plus explicite et plus rapide à décrypter.

Variables Globales

On évitera au maximum les variables globales. Si dans certain cas, leur usage est inévitable, on prendra soin de respecter la convention de hommage (i.e. préfixe g_).

Constantes

On privilégie l’utilisation du mot-clé const au lieu de #define puisque const permet d’avoir une information de type, ce qui permet au compilateur de vérifier la compatibilité de types lors de l’utilisation de la constante. On s’assurera également que les constantes soient en majuscules et que les mots de la constante soient séparés par _. Par exemple:

const int BUFFER_SIZE = 512;

Macros

Pour les macros, on utilise la même convention de nommage que pour les constantes (majuscules + _). On prendra soin d’abuser des parenthèses pour éviter les effets indésirables. Par exemple:

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

Enumérations

Pour le nom de l’énumération on utilisera la convention pour les types (majuscule sur la première lettre de chaque mot, le reste en minuscule). Pour chaque label de l’énumération, on utilisera la convention des constantes (majuscules avec _ en séparateur).

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

Blocs

Placement des accolades

On peut utiliser soit la convention K&R (pour Kernighan & Richie) qui place l’accolade ouvrante sur la même ligne que la condition:

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

soit la convention Allman (ou ANSI C) qui positionne l’accolade ouvrante à la ligne suivante:

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

Choisissez celle qui vous convient le mieux et restez consistant sur tout vos programmes ! Dans tous les cas les instructions du bloc doivent être décalées d’une tabulation vers la droite.

Utilisation des accolades

De manière générale, il est préférable de tout le temps utiliser les accolades même quand il y a une seule instruction dans le bloc. Ceci permet d’éviter les erreurs si le code est modifié et qu’une instruction dans le bloc doit être ajoutée cf:

if ( condition )
    instruction_1;
    instruction_2;

exécute systématiquement instruction_2 alors que

if ( condition )
{
    instruction_1;
    instruction_2;
}

n’exécute instruction_2 que si la condition est vérifiée. Pour maintenir la compacité du code, on peut parfois écrire le bloc sur une ligne quand il n’y a qu’une instruction. Comme par exemple:

if (condition) instruction_1;

Commentaires après les accolades fermantes

Pour les cas où vos blocs sont longs, il peut être utile d’ajouter des commentaires à la fin des accolades fermantes pour mieux se repérer dans votre programme. Par exemple:

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

Conditions

On essaiera autant que possible de mettre dans les conditions la constante à gauche de l’opérateur de comparaison (on appelle cela des yoda-conditions). Ceci permet au compilateur de détecter la confusion entre l’opérateur d’égalité == et celui d’affectation =. Par exemple:

if ( BUFFER_SIZE == size )

Et on évitera autant que possible d’impliciter les comparaisons à 0 ou NULL. Par exemple

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

est préférable à

if ( ptr_index ) ou if ( func(filename) )

Le fait que les tests soient explicites permet de plus facilement détecter des erreurs.

Forme abrégée du if-then-else

Il est possible d’utiliser la forme abrégée du if-then-else, on prendra toutefois soin de la rendre clair en évitant un then ou else trop long… l’utilisation de fonction est donc conseillée. Par exemple

( condition ) ? func_1() : func_2()

goto, continue, break

On évitera autant que possible de les utiliser puisque leur utilisation engendre des programmes dont il est difficile de tracer l’exécution. En général, il est possible d’obtenir le même résultat avec un programme mieux structuré.

Formattage

Parenthèses

Pour facilement distinguer les fonctions des mots-clés, on utilise la convention suivante:

  • La parenthèse ouvrante est insérée juste après la fonction comme: max(a, b);
  • Un espace est inséré entre la parenthèse ouvrante et le mot-clé comme: while (condition)
  • On évite les parenthèses pour les return quand elles ne sont pas nécessaires

Une variable / instruction par ligne

L’idée est de faciliter la lisibilité du code, en évitant de multiplier les déclarations de variables ou instructions multiples sur une même ligne. Ainsi on évitera:

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

et on écrira plutôt… et surtout on initialisera systématiquement les variables:

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

Affectation dans une expression

Dans le même ordre idée que le paragraphe précédent et dans un objectif de lisibilité et de maintenance du code, on évitera les affectations dans une expression. Par exemple:

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

sera écrit comme étant:

a = b + c;

d = a + r;

Divers

Pas de nombres magiques

On évitera de mettre dans les programmes des valeurs qui n’ont pas de significations. On utilisera soit des variables soit des constantes pour préciser le sens de chacune des valeurs. Ceci pour permettre l’évolutivité et la lisibilité du code.

Par exemple dans le code suivant:

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

il est impossible de savoir si le 1000 du premier for correspond à la même quantité que celui du deuxième for. Du coup, si jamais on doit changer la valeur de la condition d’arrêt du for, faut il également le faire pour le deuxième for. En l’état il est impossible de le savoir à moins de regarder finement le code. Avec la version suivante, la compréhension du code et donc sa maintenance est beaucoup plus facile:

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++ )
    {
        // ...
    }
}

Code de retour / appels système

Sauf exception (par exemple pour printf), on testera le code de retour des appels systèmes (fopen, malloc, realloc…) pour mieux tracer le déroulement du programme et détecter des bogues ou erreurs éventuels.

Utilisation de #if

Si vous voulez avoir du code qui n’est compilé que dans certains cas, il est possible d’utiliser le couple #if COND et #end. Si COND vaut 1 dans ce cas le code sera compilé sinon il sera ignoré par le compilateur. Soit le programme suivant:

#include <stdio.h>

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

Suivant la manière dont il est compilé, on obtient un résultat différent:

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:~$

En utilisant cette technique, il est très facile de commenter de larges morceaux de code en utilisant #if 0 et #end. Par exemple:

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

Fichier de validation .clang-format

Le fichier est directement téléchargeable ici .clang-format. Attention en l’enregistrant, n’oubliez pas le .

# Basic .clang-format - IMA·2A Polytech Lille
---
AccessModifierOffset: 0
AlignEscapedNewlinesLeft: true
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackParameters: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Allman
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 100
CommentPragmas: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 0
ContinuationIndentWidth: 0
Cpp11BracedListStyle: false
DerivePointerBinding: false
IndentCaseLabels: false
IndentFunctionDeclarationAfterType: false
IndentWidth: 4
Language: Cpp
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 100
PenaltyBreakComment: 100
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 100
PenaltyExcessCharacter: 1
PenaltyReturnTypeOnItsOwnLine: 20
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: Always
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
Standard: Cpp11
TabWidth: 4
UseTab: Never