XII. Débogage d'un programme▲
XII-A. Objectif▲
L'objectif de ce chapitre est de vous aider à traquer les bugs et à les corriger ;-)…
XII-B. 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
XII-B-1. Erreur à la compilation▲
Une erreur lors de la compilation est provoquée par l'exécution de la commande gcc -o :
#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.
XII-B-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.
XII-C. Un phénomène surprenant…▲
#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().
#include <stdio.h>
int
main (
) {
printf (
"
Je suis ici
\n
"
);
while
(
1
)
;
return
0
;
}
XII-D. La chasse aux bugs…▲
La démarche pour corriger un bug(18) est la suivante.
XII-D-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
;
}
XII-D-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
;
}
XII-E. Bonne chasse…▲
Exercice n°11.1 — Erreur 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
XII-F. 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émoire(19) 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 »(20).
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 !
XII-F-1. Le debugger ddd▲
Ce débogueur est très efficace pour trouver les erreurs de segmentation.
#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.
- 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.
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 débogueur 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…
XII-F-2. Une autre chasse…▲
Exercice n°11.2 — Verlan
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.
XII-F-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…
XII-G. Solutions▲
Corrigé de l'exercice n°11.1 — Erreur 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.2 — Verlan
#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
;
}
XII-H. À retenir▲
On retiendra que pour trouver un bug, la méthode est toujours la même :
- on tente de le reproduire à tous les coups et on note la séquence,
- on cherche à isoler le plus précisément possible l'endroit où le problème est apparu en injectant des mouchards (trace),
- dans le cas d'une erreur de segmentation, on tente d'utiliser un débogueur.
XII-H-1. Le débogueur ddd▲
Pour utiliser ddd :
- Compilez le programme avec l'option ‐g selon : gcc -o programme programme.c -g
- Exécutez : ddd programme