Conseils de codage en C/Recherche des erreurs
L'application de ces conseils facilite la recherche des erreurs et permet d'en éviter certaines.
Des identificateurs plus parlants (c_rec_1)
modifierUtilisez des identificateurs parlants pour les variables. Le nom d’une variable devra rappeler son rôle ou son utilité.
Justification
modifierDes 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
modifierPour désigner un numéro de maille.
- Mauvais : n
- Bon : numMaille
Sortie de boucle (c_rec_2)
modifierLa sortie d’une boucle (do, while, for) doit se faire par la condition de test.
Justification
modifierUn 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
modifierfor (int i = indiceMax; table[i] != NULL; i++)
{
free(table[i]);
}
for pour contrôler les boucles (c_rec_3)
modifierL’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
modifierL’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)
modifierLes expressions arithmétiques et logiques doivent être placées entre parenthèses.
Justification
modifierCette 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
modifierOn 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)
modifierLes macros doivent être écrites avec des parenthèses autour de leurs paramètres.
Justification
modifierLors 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
modifierMauvais : #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)
modifierJustification
modifierAu-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)
modifierNe pas placer d’opérateurs unaire (++, --) dans les paramètres d’appel des macros.
Justification
modifierLe 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)
modifierOn protégera chaque fichier d’en-tête .h des inclusions multiples.
Justification
modifierLorsqu'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
modifierPour 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)
modifierLe calcul d’une expression complexe au sein d’une condition est à éviter.
Justification
modifierFacilite 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)
modifierLes fonctions doivent être prototypées, une fonction ou une variable ne doit pas avoir de type par défaut.
Justification
modifierFacilite l’analyse. Permet un meilleur contrôle de la correspondance arguments - paramètres. La norme C99 interdit de transgresser cette règle.
Outils
modifierLe 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)
modifierUne 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
modifierLorsque 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
modifierDans 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)
modifierJustification
modifierSelon 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Éviter l’utilisation des pointeurs. Préférer l’utilisation des indices de tableau.
Justification
modifierL’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)
modifierUtiliser le mot-clé ANSI const pour définir :
- Une variable non modifiable.
- Un argument non modifiable par une fonction.
Justification
modifierRejette, dès la phase de compilation, certaines modifications abusives.
Affectations et identificateurs de tableau (c_rec_15)
modifierÊtre prudent lors de l'utilisation des identicateurs de tableau et des pointeurs sur chaîne constante.
Justification
modifierLes 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
modifierVoici 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*.