Conseils de codage en C/Recherche des erreurs

Un livre de Wikilivres.

L'application de ces conseils facilite la recherche des erreurs et permet d'en éviter certaines.

Des identificateurs plus parlants (c_rec_1)[modifier | modifier le wikicode]

Utilisez des identificateurs parlants pour les variables. Le nom d’une variable devra rappeler son rôle ou son utilité.

Justification[modifier | modifier le wikicode]

Des noms de variable trop courts et souvent sans signification (a, n, x...) ne permettent pas aux relecteurs de faire la relation entre l'identificateur informatique et la réalité représentée. La recherche des variables est alors difficile et les risques d'avoir deux identificateurs dont les portées se superposent sont augmentés.

Exemple[modifier | modifier le wikicode]

Pour désigner un numéro de maille.

  • Mauvais : n
  • Bon : numMaille

Sortie de boucle (c_rec_2)[modifier | modifier le wikicode]

La sortie d’une boucle (do, while, for) doit se faire par la condition de test.

Justification[modifier | modifier le wikicode]

Un algorithme bien conçu ne doit pas nécessiter de sortie prématurée dans le corps de boucle. La programmation structurée permet d'éviter le style spaghetti des premiers programmeurs. Ce mode de programmation ancien rendait le suivi du déroulement difficile à l'aide d'un débugueur. Il fallait mettre des points d'arrêt sur chaque sortie de boucle potentiel. La logique du programme était très difficile à appréhender par la personne chargée d'effectuer des modifications.

Les instructions goto, continue, break (hors switch) sont donc vivement déconseillées.

Exemple[modifier | modifier le wikicode]

for (int i = indiceMax; table[i] != NULL; i++)
{
   free(table[i]);
}

for pour contrôler les boucles (c_rec_3)[modifier | modifier le wikicode]

L’instruction d’une boucle for ne doit contenir aucune instruction vide. De plus, chacun de ses membres doit porter sur au moins une variable commune.

Justification[modifier | modifier le wikicode]

L’instruction for doit permettre le contrôle total d’une itération. Si une instruction est vide, cela veut dire que la boucle aurait pu être écrite sous une autre forme (do ou while).

Mettre des parenthèses dans les expressions (c_rec_4)[modifier | modifier le wikicode]

Les expressions arithmétiques et logiques doivent être placées entre parenthèses.

Justification[modifier | modifier le wikicode]

Cette règle évite les erreurs d’interprétation dues aux règles d’associativité du langage C. Son non respect peut conduire à des erreurs dans le codage des expressions mathématique ou de test.

Exemple[modifier | modifier le wikicode]

On veut tester si n est pair : n & 1 effectue un ET logique de bit entre n et 1, mais l'opérateur relationnel == est prioritaire par rapport à &, l’expression ( n & 1 == 0 ) sera donc toujours fausse.

  • Mauvais : if ( n & 1 == 0 ) ...
  • Bon : if ( ( n & 1 ) == 0 ) ...

Mettre des parenthèses autour des paramètres des macros (c_rec_5)[modifier | modifier le wikicode]

Les macros doivent être écrites avec des parenthèses autour de leurs paramètres.

Justification[modifier | modifier le wikicode]

Lors du remplacement des paramètres formels par les paramètres effectifs, il peut y avoir des problèmes de priorité des opérateurs dans les expressions résultats.

Exemple[modifier | modifier le wikicode]

Mauvais : #define double(a) 2 * a :
L’appel double(2+1) sera étendu en 2 * 2 +1 : vaudra 5 au lieu de 6.

Meilleur : #define double(a) 2 * (a) :
L’appel double(2+1) sera étendu en 2 * (2 +1) : vaudra 6

Ne pas écrire de macro de plus de 5 instructions (c_rec_6)[modifier | modifier le wikicode]

Justification[modifier | modifier le wikicode]

Au-delà de 5 instructions, le déroulement est assimilable à un traitement, et doit faire partie intégrante d’une fonction. De plus, les macros instructions ne permettent pas un contrôle strict de leurs paramètres.

Si l'utilisation d'une fonction entraîne une dégradation des performances, il faut recourir à l’inlining. Le mot clé C99 inline répond à ce problème.

Pas d'opérateurs unaire à l'appel des macros (c_rec_7)[modifier | modifier le wikicode]

Ne pas placer d’opérateurs unaire (++, --) dans les paramètres d’appel des macros.

Justification[modifier | modifier le wikicode]

Le résultat est bien souvent imprévisible par suite des effets de bord, surtout en cas de répétition du paramètre dans la définition de la macro.

Éviter les problèmes d'inclusion multiple (c_rec_8)[modifier | modifier le wikicode]

On protégera chaque fichier d’en-tête .h des inclusions multiples.

Justification[modifier | modifier le wikicode]

Lorsqu'un fichier d'entête (.h) est inclus plusieurs fois directement, un test en son début permet d'éviter les erreurs de compilation dues à des déclarations répétées :

Exemple[modifier | modifier le wikicode]

Pour le fichier graphique.h:

#ifndef GRAPHIQUE_H
#define GRAPHIQUE_H
   // corps du fichier d’en-tête
   // ...
#endif

Mélange d'opérateur arithmétique et relationnel (c_rec_9)[modifier | modifier le wikicode]

Le calcul d’une expression complexe au sein d’une condition est à éviter.

Justification[modifier | modifier le wikicode]

Facilite l’analyse des sources, évite de superposer les erreurs de priorité des opérateurs arithmétiques à ceux des opérateurs relationnels.

Prototypes de fonction (c_rec_10)[modifier | modifier le wikicode]

Les fonctions doivent être prototypées, une fonction ou une variable ne doit pas avoir de type par défaut.

Justification[modifier | modifier le wikicode]

Facilite l’analyse. Permet un meilleur contrôle de la correspondance arguments - paramètres. La norme C99 interdit de transgresser cette règle.

Outils[modifier | modifier le wikicode]

Le contrôle du peut être effectué en utilisant le compilateur C avec options les plus strictes ou un outils qualité comme lint ou splint.

Les variables ne doivent pas se masquer (c_rec_11)[modifier | modifier le wikicode]

Une variable ne doit pas en masquer une autre. Cela se produit lorsque deux variables ont le même nom dans des blocs imbriqués.

Justification[modifier | modifier le wikicode]

Lorsque plusieurs entités sont désignées par le même nom et ne peuvent être distinguées que par leur position par rapport aux structures de contrôle, cela réduit la lisibilité du code et favorise l’apparition d’erreurs, en particulier lors de la maintenance du source.

Exemple à ne pas suivre[modifier | modifier le wikicode]

Dans l'extrait de source suivant, les variables i se masquent.

// Première déclaration de i
int i = 0;
// ...
{
   // Deuxième déclaration de i
   int i = 0;
   // ...
   i++;
   // ...
}

Si la deuxième déclaration de i est supprimée, alors il y a un risque pour que l’incrémentation ne le soit pas, ce serait alors la variable déclarée hors du bloc qui serait utilisée, ce qui ne serait pas le comportement souhaité. Le code resterait alors valide et aucun problème ne serait détectée.

Pas de suppression de l'attribut const (c_rec_12)[modifier | modifier le wikicode]

Justification[modifier | modifier le wikicode]

Selon le compilateur, il est possible que les données const soient stockées dans une zone accessible en lecture, mais pas l’écriture.

Limiter l'utilisation des pointeurs (c_rec_13)[modifier | modifier le wikicode]

Éviter l’utilisation des pointeurs. Préférer l’utilisation des indices de tableau.

Justification[modifier | modifier le wikicode]

L’utilisation abusive de pointeurs est la source de graves dysfonctionnements très difficiles à détecter. Les programmeurs habitués à d’autres langages sont souvent perdus face à certaines subtilités du C. Pour les parcours de tableaux, il est préférable d’utiliser des indices (tab[i]) au lieu du déréférencement par pointeur.

Utiliser le mot-clé const (c_rec_14)[modifier | modifier le wikicode]

Utiliser le mot-clé ANSI const pour définir :

  • Une variable non modifiable.
  • Un argument non modifiable par une fonction.

Justification[modifier | modifier le wikicode]

Rejette, dès la phase de compilation, certaines modifications abusives.

Affectations et identificateurs de tableau (c_rec_15)[modifier | modifier le wikicode]

Être prudent lors de l'utilisation des identicateurs de tableau et des pointeurs sur chaîne constante.

Justification[modifier | modifier le wikicode]

Les types int a[] et int* a sont complètement différents, même si, une fois déclarés, leur usage parait identique. Le programmeur doit être particulièrement vigilant : par exemple, beaucoup d’éditeurs de liens confondraient un int* a et un int a[] définis dans deux modules (.o) différents. Ceci est susceptible de provoquer une erreur fatale.

Exemple[modifier | modifier le wikicode]

Voici une illustration de la différence entre ces deux types [C FAQ] :

char a[] = "hello";
char* p = "hello";

La variable “ a ” occupe 6 octets dans l’espace de la mémoire dynamique. Cette zone sera désallouée lorsque la variable sortira de son espace de validité. La variable p occupe 4 octets (taille courante d’un pointeur). Elle est un pointeur qui référence une région de la mémoire non modifiable. Une nouvelle valeur peut être affectée à “p”, mais pas à “a”. En fait, un bon compilateur devrait imposer ici le type const char*.