Erreurs Classiques

Cette page contient une liste de quelques erreurs classiques que l'on peut faire en programmant en C. Un des gros problèmes de la programmation (en générale, mais surtout en C) est qu'on peut avoir des programmes faux qui ont l'air de marcher mais vont planter dans certaines situations (dans un environement different ou après un long moment).

*ptr++

int i;
scanf("%d", i);

#define f(p) p*5
...
f(3+4)

if (a = 10) {...

for (i=0; i<10; i++);
  faire_qqch_plusieurs_fois;

char* a, b

int i;
int t[10];
for (i=0; i<=10; i++)
  t[i]=...;

Il y a deux fautes que beaucoup d'étudiants font, surtout s'ils savent programmer en Java :

Explications. Soit le code C suivant :

int n=5;

int foo() {
  int b;
}

int main() {
  int a;
  foo();
}

Voici ce qui se passe au niveau de la mémoire :

  1. la pile (stack) de main est allouée. Elle contient entre autre la variable a.
  2. le code de main est executé : appel de la fonction foo.
  3. la pile de foo est allouée. Elle contient entre autre la variable b.
  4. le code de foo est executé.
  5. la pile de foo est libérée. La variable b n'existe plus et sa place mémoire peut être réutilisée.
  6. on revient à l'exécution de main : fini.
  7. la pile de main est désallouée. La variable a n'existe plus et sa peut être réutilisée (par exemple par le système d'exploitation).

Notes :

La variable n n'est pas crée sur une pile, mais ailleurs, dans la section « static storage » (aussi appelée « initialized data segment ») qui est une zone mémoire en lecture seulement (« read-only »).

En fait il n'y a pas une pile par fonction, mais une seule pile par programme (une pile par thread en fait). Quand une pile est alouée, elle est simplement ajoutée à la fin de la précédente. Quand une pile est désalouée, les données sont encore accessibles, jusqu'à qu'une autre pile vient se mettre par-dessus.

Les parametres des fonctions sont passés sur la pile. Passer directement des structures (au lieu de pointeurs), provoque donc des copies vers/depuis les différentes piles.

Voici maintenant un code C qui crée un rectangle.

 typedef struct _rectangle {
  int width;
  int height;
} rectangle;

Voici deux premières versions contiennent des erreurs :

Code 1 - ne repecte pas la seconde règle

rectangle create(int w, int h) {
  rectangle r;
  r.width = w;
  r.height = h;
  return r;
}

int main() {
  rectangle r1 = create(5, 6);
  rectangle r2 = create(12, 3);
}

Ce code (qui fonctionne correctement) effectue en effet une copie de la structure au niveau du return.

Tentative de correction en utilisant des pointeurs :

Code 2 - ne respecte pas la première règle

rectangle* create(int w, int h) {
  rectangle r;
  r.width = w;
  r.height = h;
  return &r;
}

int main() {
  rectangle* r1 = create(5, 6);
  rectangle* r2 = create(12, 3);
}

C'est pire qu'avant car ce code est carrément faux ! Il retourne en effet l'adresse d'une variable locale... On court tout droit au «segmentation fault»

Voici maintenant deux solutions qui sont correctes :

Solution 1 - allocation dynamique (sur le tas) de l'objet retourné

rectangle* create(int w, int h) {
  rectangle* r = (rectangle*) malloc(sizeof(rectangle));
  r->width = w;
  r->height = h;
  return r;
}

int main() {
  rectangle* r1 = create(5, 6);
  rectangle* r2 = create(12, 3);
  // ne pas oublier ensuite de faire free(r1)
et free(r2) !
  ...
}

Solution 2 - objets sur la pile, mais celle de main : passage par référence

void create(rectangle* r, int w, int h) {
  r->width = w;
  r->height = h;
}

int main() {
  rectangle r1, r2;
  create(&r1, 5, 6);
  create(&r2, 12, 3);
}
Notez que dans cette dernière solution create perd de son sens premier car en tout rigueur ne crée plus rien du tout. Cette dernière fonction devrait donc plutôt s'appeler init ou quelque chose comme ça.

retour à la page complément