Outils

 Haut
 
Le C en 20 heures
Chapitre
11
Débogage d’un programme


11.1 Objectif

L'objectif de ce chapitre est de vous aider à traquer les bugs et à les corriger ;-)…

11.2 Deux types d'erreurs

On fera la différence entre les deux types d'erreurs suivants :
erreur de compilation ou d'édition de liens
erreur d'exécution

11.2.1 Erreur à la compilation

Une erreur lors de la compilation est provoquée par l'exécution de la commande gcc -o :
Vous tapez gcc -o essai essai.c pour le programme suivant :
#include <stdio.h> 
int main () {
floatt f ;

return 0 ;
}
Le compilateur vous renvoie un message du type :
essai.c : 3 : 'floatt' undeclared (first use in this function)
Une erreur d'édition de liens survient lorsque, par exemple, on utilise une fonction d'une bibliothèque séparée, sans penser à joindre la bibliothèque lors de la création de l'exécutable.

11.2.2 Erreur d’exécution

Une erreur d'exécution est provoquée lors de l'exécution du programme, c'est à dire lorsque la compilation et l'édition de liens se sont bien réalisées mais qu'il y a un problème lorsque l'on teste l'exécutable.
#include <stdio.h> 
int main () {
float f=1/0;

return 0 ;
}
La compilation ne pose pas de problèmes. Lors de l'exécution du programme (./essai), l'ordinateur affiche un message du type : Floating point exception. En effet, une division par zéro est interdite.

11.3 Un phénomène surprenant…

Saisissez le programme suivant, compilez‐le et enfin testez‐le…
#include <stdio.h> 
int main () {
printf ("Je suis ici") ;
while (1)
;

return 0 ;
}
Rien ne s'affiche ? ! En fait, pour des raisons d'optimisation système, un printf() est conservé dans un buffer de ligne jusqu'à réception d'un \n ou exécution d'un fflush().
Essayez :
#include <stdio.h> 
int main () {
printf ("Je suis ici\n") ;
while (1)
;

return 0 ;
}

11.4 La chasse aux bugs…

La démarche pour corriger un bug1 est la suivante.

11.4.1 Localiser et produire le bug

Prenons l'exemple du programme suivant qui n'affiche rien pour la raison précédemment évoquée (le problème de l’ \n) :
#include <stdio.h> 

int main () {
int i ;
for (i=0 ; i<100 ; i++)
printf("i=%d",i) ;
while (1)
;
return 0 ;
}
Le rajout de « mouchards » (un mouchard est ici simplement un printf) dans le programme nous permettra de localiser le bug.
#include <stdio.h> 
int main () {
int i ;
printf("1) Je suis ici\n") ; /* 1 er mouchard */

for (i=0 ; i<100 ; i++)
printf("i=%d",i) ;
printf("2) Je suis ici\n") ; /* 2 eme mouchard */

while (1)
;
return 0 ;
}

11.4.2 Corriger le bug

La phase précédente vous a permis de savoir d'où venait le problème, en revanche, celle‐ci ne l'a pas corrigé.
Prenons l'exemple suivant :
#include <stdio.h> 
int main () {
int i,j;
i=0;
j=0;
printf("1) Je suis ici\n") ;
if ((i==0) && (i=j)) {
printf("2) Je suis ici\n");
...
}
return 0 ;
}
Vous êtes persuadé que l'ordinateur devrait afficher 2) Je suis ici or il n'en est rien ? ! Vous en êtes à invoquer un bug dans le compilateur ? ! L'ordinateur ne fait que ce que vous lui demandez. Vous allez donc affiner votre traçabilité en plaçant des mouchards pour voir précisément le contenu des variables.
#include <stdio.h> 
int main () {
int i,j;
i=0;
j=0;
printf("1°) Je suis ici\n");
printf("i=%d j=%d\n",i,j);
if ((i==0) && (i=j)) {
/* (i=j) => i vaudra 0, et 0 est identique à faux !!! */
printf("2°) Je suis ici\n");
...
}
return 0 ;
}

11.5 Bonne chasse…

Exercice n°11.1Erreur moyenne
Copiez le programme suivant, puis corrigez‐le.
#include <stdio.h> 
int main () {
int i, somme;
for (i=0 ; i<10 ; i++);
printf ("i=%d\n",i) ;
somme += i;
printf("La moyenne vaut:%d",somme/i) ;
return 0 ;
}
Ce programme est censé afficher ceci à l'écran :
i=0
i=1
...
i=9
La moyenne vaut: 4.50000

11.6 Erreurs d'exécution : les erreurs de segmentation…

Ce type d'erreur apparaît lorsque votre programme accède à une zone de la mémoire qui lui est interdite : vous êtes sur un segment de la mémoire2 sur lequel vous n'avez pas le droit de travailler.
Le programme suivant peut provoquer de telles erreurs :
#include <stdio.h> 
int main () {
int i=0;
scanf("%d",i);

return 0 ;
}
Soit vous voyez tout de suite l'erreur… soit vous ajoutez des mouchards :
#include <stdio.h> 
int main () {
int i=0;
printf("1°) Je suis ici\n") ;

scanf("%d",i);
printf("2°) Je suis ici\n");

return 0 ;
}
Ces mouchards vous permettront rapidement de voir que le problème provient de la ligne scanf("%d",i) car seul le message « 1°) Je suis ici » s'affiche et pas le message « 2°) Je suis ici »3.
Le problème vient donc de i qui vaut 0… le scanf va tenter de stocker ce que vous venez d'entrer au clavier à l'adresse mémoire 0 (NULL) ! Cette dernière est réservée au système d'exploitation, d'où l'erreur…
Il en va de même du programme ci‐dessous qui pourrait poser des problèmes du fait que l'on risque de sortir des bornes du tableau.
#include <stdio.h> 
#define TAILLE 10

int main () {
int tab[TAILLE];

tab[TAILLE+10]=100;

return 0 ;
}
Ce programme peut planter ou non. Ce type d'erreur conduit le plus souvent à des bugs aléatoires, les plus difficiles à corriger !

11.6.1 Le debugger ddd

Ce debugger est très efficace pour trouver les erreurs de segmentation.
Copiez, compilez, exécutez le programme suivant.
#include <stdio.h> 
#include <string.h>
int main () {
int * p=NULL;
*p=123;
printf("\n je suis ici...\n");
}
Le programme ne fonctionne pas et provoque une erreur de segmentation. Nous allons donc le débugger avec ddd.
Faites :
gcc -o essai essai.c -g
ddd essai
fermez les petites fenêtres « parasites » qui apparaissent au lancement de ddd
cliquez sur le bouton run (il faut parfois chercher un peu dans les menus)…
L'option -g de la ligne de compilation permet de compiler le programme en y incluant les informations supplémentaires utiles au débogage.
undefined
Figure 11.1 - Débogage d’un programme
Lorsque vous faites p=NULL ;, vous placez donc la valeur 0 dans cette variable. Ceci signifie que p pointe sur un élément mémoire qui n'est pas accessible par votre programme en écriture. Or, vous faites *p=123 ; qui revient à vouloir écrire la valeur 123 à l'adresse NULL.
Le debugger ddd vous indique alors quelle ligne a provoqué l'erreur de segmentation. Sur un programme de plusieurs centaines, voire plusieurs milliers de lignes, cette aide est particulièrement appréciable.
Moralité : en cas d'erreur de segmentation, tentez tout d'abord un ddd

11.6.2 Une autre chasse…

Exercice n°11.2Verlan
Soit le programme suivant qui doit afficher la chaîne de caractères chaine à l'envers, et ce, caractère par caractère :
#include <stdio.h> 
#include <string.h>

int main () {
int i;
char chaine[]="! éuggubéd tse emmargorp el";

for (i=strlen(chaine); i =! 0; i--)
printf("%s",chaine[i]) ;
return 0 ;
}
Corrigez le programme précédent.

11.6.3 Dernière sournoiserie…

Testez le programme suivant :
#include <stdio.h> 

int main () {
int i;
int i1,i2 ;
char c1,c2;

printf("1) Entrez un nombre: ");
scanf("%d",&i1);
printf("2) Entrez un nombre: ");
scanf("%d",&i2);

printf("1) Entrez une lettre: ");
scanf("%c",&c1);
printf("2) Entrez une lettre: ");
scanf("%c",&c2);

printf("1) J'ai récupéré lettre 1:%d\n",(int)c1);
// renverra 10 c.à.d le code ascii de '\n'
printf("2) J'ai récupéré lettre 2:%d\n",(int)c2);
}
On voit que l'\n subsiste du premier caractère et pollue la variable c2. En effet, quand vous faites un :
scanf("%c",&c1);
vous demandez à l'ordinateur de lire un unique caractère. Du coup l'\n restera de côté (pour l'instant).
Une solution pour remédier à ce travers consiste à utiliser systématiquement la fonction gets(chaine) à la place de chaque scanf de la façon suivante.
/* ===== Version non corrigée ===== */
int i;
char c;
printf("Entrez un caractère:");
scanf("%c",&c);

printf("Entrez un chiffre:");
scanf("%d",&i);
/* ===== Version corrigée ===== */
int i;
char c;
char chaine[100];

printf("Entrez un caractère:");
gets(chaine);
c=chaine[0];

printf("Entrez un chiffre:");
gets(chaine);
sscanf(chaine,"%d",&i) ;
Cette solution n'est en fait pas viable d'un point de vue sécurité du code (vous obtenez d'ailleurs peut-être un warning). En effet que se passera‐t‐il quand, au lieu de saisir un caractère, vous en saisirez 200 ? Le tampon de 100 caractères sera dépassé et un problème apparaîtra. Nous n'aborderons pas ici le problème de la saisie sécurisée, mais soyez tout de même conscient(e) du problème potentiel…

11.7 Solutions

Corrigé de l’exercice n°11.1Erreur moyenne
#include <stdio.h> 
int main () {
int i, somme=0;
for (i=0 ; i<10 ; i++) {
printf("i=%d\n",i) ;
somme = somme + i;
}
printf("La moyenne vaut:%f",(float) somme/i) ;
return 0 ;
}
Corrigé de l’exercice n°11.2Verlan
#include <stdio.h> 
#include <string.h>

int main () {
int i;
char chaine[]="! éuggubéd tse emmargorp el";

for (i=strlen(chaine); i != 0; i--)
printf("%c",chaine[i]) ;
return 0 ;
}

11.8 À retenir

On retiendra que pour trouver un bug, la méthode est toujours la même :
1.
on tente de le reproduire à tous les coups et on note la séquence,
2.
on cherche à isoler le plus précisément possible l'endroit où le problème est apparu en injectant des mouchards (trace),
3.
dans le cas d'une erreur de segmentation, on tente d'utiliser un debugger.

11.8.1 Le debugger ddd

Pour utiliser ddd :
Compilez le programme avec l'option ‐g selon : gcc -o programme programme.c -g
Exécutez : ddd programme


1. Un bug est un défaut dans un programme produisant des anomalies de fonctionnement.
2. Segment de mémoire (d'après Wikipédia) : espace d'adressage indépendant défini par deux valeurs : l'adresse où il commence et sa taille.
3. N’oubliez pas les \n dans vos printf pour la raison évoquée plus haut…
Discussion :  1  

Discussion :  1  

Administrateur du livre

Creative Commons-BY-SA

RSS   Poule ou l'oeuf

     
One minute, please...

Fermer