« Conseils de codage en C/Recherche des erreurs » : différence entre les versions

Un livre de Wikilivres.
Contenu supprimé Contenu ajouté
CaBot (discussion | contributions)
m →‎Limiter l'utilisation des pointeurs (c_rec_13) : indirection (&) → déréférencement (*)
Ligne 136 : Ligne 136 :


===Justification===
===Justification===
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 d’indirection par pointeur.
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)==
==Utiliser le mot - clé const (c_rec_14)==

Version du 3 octobre 2014 à 15:59

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

Des identificateurs plus parlants (c_rec_1)

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

Justification

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

Pour désigner un numéro de maille.

  • Mauvais : n
  • Bon : numMaille

Sortie de boucle (c_rec_2)

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

Justification

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

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

for pour contrôler les boucles (c_rec_3)

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

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)

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

Justification

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

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)

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

Justification

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

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)

Justification

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)

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

Justification

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)

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

Justification

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

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)

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

Justification

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)

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

Justification

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

Outils

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)

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

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

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)

Justification

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)

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

Justification

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)

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

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

Justification

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

Affectations et identificateurs de tableau (c_rec_15)

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

Justification

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

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*.