Les erreurs fréquentes en C

[Dal] Messages postés 6174 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 2 février 2024 - 14 juin 2022 à 20:13
Note : fiddy est l'auteur d'origine de l'astuce.



Introduction

Le C, inventé en 1972 par Ken Thompson et Dennis Ritchie, est un des premiers langages de programmation système. Le C est encore aujourd'hui l'un des langages les plus utilisés. Enseigné souvent dans les écoles post-bacs, il est parfois réputé très simple, notamment grâce à sa syntaxe.

Si faire un programme fonctionnel en C est certes relativement simple dès lors que l'on a une bonne connaissance des pointeurs, il est beaucoup plus dur de réaliser un programme robuste.

Les deux principales raisons à l'origine de programmes vulnérables sont l'ignorance du programmeur et la nécessité de sortir un programme rapidement (délai à respecter).

Les conséquences sont dans les deux cas dramatiques. Pratiquement tous les jours, nous entendons parler de programmes vulnérables, nous obligeant à mettre à jour l'application sous peine de porter atteinte à la sécurité de notre machine. De nombreuses erreurs sont classiques et pourraient être évitées en confrontant son code à une fiche énumérant les erreurs les plus fréquentes.

Cette fiche pratique listera, de manière non exhaustive, quelques exemples que l'on retrouve bien trop souvent.

Confusion entre le C et le C++

Le C et le C++ sont deux langages régis par des normes différentes. Il s'agit donc de deux langages distincts. Et contrairement à ce que beaucoup pensent, toutes les instructions en C ne sont pas valables en C++. Veillez donc bien à mettre votre post dans la bonne catégorie !

Mauvais prototypage du main()

Prototypes erronés sont souvent vus :
  • main ()
  • void main ()
  • void main (void)


Les deux seuls prototypes admis du main() sont :
int main (void)
int main (int argc, char **argv) / int main (int argc, char *argv[]).

Fin de programme

Corollaire de l'erreur précédente, le main() doit se finir par :
return EXIT_SUCCESS; ou return 0; /*la norme indique que EXIT_SUCCESS==0*/
return EXIT_FAILURE;

Note : L'utilisation de EXIT_SUCCESS ou EXIT_FAILURE nécessite : #define <stdlib.h>

char *buffer; scanf("%s", buffer);

Erreur Segmentation Fault assurée ! La 1ère instruction crée une variable qui contiendra une adresse pointant sur une autre. Le problème est qu'il faut définir cette adresse et que la zone pointée doit être suffisamment grande pour accueillir les données. Il faut donc utiliser l'une des deux lignes suivantes :
char *buffer = malloc(256*sizeof *buffer);
char buffer[256]; /*si on ne souhaite pas faire d'allocation dynamique*/ 

Confusion entre les opérateurs de comparaison et d'affectation

Exemple d'erreur : if (vie = 5)
Cela ne compare pas la variable vie à 5, mais effectue une affectation vie=5 et renvoie 5 (true). Autrement dit, la condition sera toujours vraie.
L'opérateur de comparaison est le double égal ==.
Il faut donc écrire :
if (vie == 5)
.

Mauvais prototypage des fonctions sans argument

Exemple d'erreur : int affichage ()
La définition du prototype est incomplète. Pour définir un prototype sans argument, il faut préciser void en argument. Cela signifie que la fonction accepte 0 argument. On veillera donc à écrire :
type fonction (void)


Note : avec un compilateur conforme à la norme C11, cette précaution n'est plus obligatoire. Il est recommandé cependant de la suivre afin de rendre le code conforme aux normes précédentes.

Mauvaise recopie des chaînes de caractères

Exemple d'erreur : char buffer[256];
buffer = "coucou";

Pour recopier une chaîne dans un tableau, il faut utiliser la fonction strcpy () ou encore mieux strncpy ().
Exemple :
strcpy (buffer, "coucou");

Utilisation de fonctions sans contrôle sur la taille

Exemple d'erreur : char buffer[256];
gets (buffer) ou scanf ("%s", buffer);

À ne jamais utiliser. En effet, si l'utilisateur entre une chaîne trop grande, cela provoquera un plantage de votre programme et cela, dans le meilleur des cas !
La bonne façon est d'utiliser :
fgets (buffer, sizeof buffer, stdin);

À noter que fgets (), s'il y a de la place, stockera le retour chariot '\n' dans le buffer. Si cela vous gêne, vous pouvez le supprimer (http://fiddy.free.fr/blog/index.php?post%2F2008%2F11%2F22%2FLe-C%2C-pas-si-facile-que-%C3%A7a%29_=

Cast inutile de malloc ()

Exemple d'erreur :
char *p = (char *) malloc(...);
Dans les toutes premières versions du C (C K&R), c'était effectivement la bonne façon de faire car malloc retournait alors un char *. En C++, c'est aussi la bonne façon de faire (bien qu'il existe l'instruction new qui est plus appropriée). Mais depuis la normalisation du C89, malloc () renvoie un void * qui sera promu automatiquement en le type ad hoc. Donc le cast est totalement inutile. Pis, cela est déconseillé puisque dans certaines versions de compilateur, cela masque l'oubli de l'include <stdlib.h> par le programmeur. Donc, la bonne façon est :
char *p = malloc (...);

Note, cette remarque s'applique à toutes les fonctions renvoyant des void * comme calloc(), realloc(), etc.

L'utilisation des float

Exemple : float a;
Certes, ce n'est pas une erreur. Mais, sauf si la taille mémoire compte beaucoup, vous devriez toujours privilégier les double.
On préférera donc :
double a

Utilisation de %lf dans printf()

Exemple d'erreur : double a;
printf ("%lf", a);

Une erreur fréquence consiste à penser que %lf équivaut à double. Si cela est vrai dans un scanf (), il en est autrement dans le printf (). Il faudra écrire :
printf ("%f", a);

Mélange de déclarations et d'instructions

Exemple d'erreur : printf ("Petite erreur\n");
int b;

On ne mixe pas les déclarations et les instructions. On met toutes les déclarations de variable en 1er, les instructions ensuite.
Cela donne donc :
int b;
printf ("Petite erreur");

Utilisation d'une variable dans le printf ()

Exemple d'erreur : fgets (variable, sizeof variable, stdin);
print(variable);

Cela introduit une vulnérabilité dans votre code.
Il faut écrire :
printf("%s\n", variable);