Le langage C
Quatrième partie

Par?:
Guilhem de Wailly (gdw@erian-concept.com)

Résumé
Dans cette série d'articles, nous présentons le langage C. Il ne s'agit pas de r??crire les ouvrages de référence donnés en annexe, mais de donner, au travers du C, une méthode de programmation basée sur la notion d'interface.
Au fur et à mesure de notre progression, nous décrirons les éléments indispensables du langage et conduirons le lecteur à consulter les ouvrages de références. Le but poursuivit est clairement de parvenir à construire des programmes de manière segmentée.
Dans cet article, nous discutons de la portée des variables, de la déclaration et de la définition des fonctions C.
Cette présentation est bas?e sur les outils GNU comme gcc, ld et ar et le débogueur gdb. Elle peut etre transposée dans d'autres environnements comme les plates-formes Microsoft, bien que les outils y soit souvent complètement intégrés, confisquant du meme coup la souplesse, la puissance et la liberté à l'utilisateur.
Errata
Dans le précédent article, le dernier programme présente une petite erreur: Les chaines de format utilisées contiennent %d pour imprimer des nombres rèels, au lieu de %f.
Introduction
Ce mois-ci, nous allons nous intéresser aux débogueurs que Linux nous propose. Tous les d?bogueurs qui traitent des binaires compilés avec le compilateur C gcc sont basés sur le débogueur GNU gdb.
Un programme
Pour nous aider à déboguer, il nous faut un programme. Nous reprenons le dernier programme présenté dans le précédent Linux Magazine?:
/* FICHIER ex.c */

#include <stdio.h>

/* d?claration des fonctions */
double f (double a);	/* format long */
double g (double);	/* format court */

/* d?finition des fonctions */
double f (double a) {
printf ("a = %f\n", a);
if (a <= 0.01) {
  return (0.0);
}
else {
  return  g (a * 2.0);
}
}

double g (double x) {
printf ("x = %f\n", x);
/* syntaxe du if sans accolades */
if (x <= 0.01) return (0.0);
else           return  f (x / 3.0);
}

/* programme principal */
void main (void) {
f (10);
}
Ce programme est composé de plusieurs fonctions, main, g et f. La fonction main est le point d'entrée du programme : c'est par elle que va commencer l'exécution. 
Les deux premières lignes de code sont des prototypes des fonctions, afin de résoudre le problème de la récursion mutuelle entre les fonctions f et g (le fait que f utilise g et que g utilise f).
Ce programme va nous servir d'exemple pour utiliser les différents débogueurs présentés dans cet article. 
Pour pouvoir déboguer un programme, il faut le compiler avec une option spécifique qui ajoute des informations au binaire produit. Ces informations sont regroupées dans une table appelée table des symboles.
La table des symboles permet de retrouver le nom du fichier source et la ligne de code concernée à partir d'une position dans l'exécutable.
Les binaires compilés avec l'option de débogage sont plus volumineux est plus lent que lorsqu'ils sont compilés sans cette option.
L'option de débogage est introduite dans gcc par -g. Nous compilons donc notre programme ex.c avec :
$ gcc -g ex.c
Cette commende fabrique un fichier a.out qui est le binaire produit à partir du programme source contenu dans le fichier ex.c. On peut produire un fichier en spécifiant explicitement un autre nom en utilisant l'option -o fichier.
GDB
Gdb est un débogueur ``ligne de commande'', c'est à dire qu'il se pilote en tapant les commandes sur le prompt. Bien que soient interface utilisateur est très primitive, le d``bogueur gdb est extrèmement puissant.
Le fait que cette interface soit très simple peut aussi rendre service lorsque l'on souhaite d`éboguer un programme via un terminal lent. Avec un peu d'habitude, finalement, ce n'est pas si mal !
D?marrage
Pour commencer la session de débogage, il faut invoquer gdb avec le nom du programme binaire :
$ gdb a.out
>	gdb a.out
GNU gdb 4.17.0.4 ...
(gdb)
Nous sommes maintenant dans le débogueur. Le système est en attente et le programme n'est pas encore en cours d'exécution. Pour l'exécuter, nous tapons :
(gdb) run
>	Starting program: a.out
a = 10.000000
x = 20.000000
...
a = 0.006766

Program exited with code 040.
(gdb)
Le système se retourne dans le meme état que précédemment. Le débogueur ne nous a pas beaucoup servit, non ?
Point d'arr?t
Pour y voir quelque chose, il est nécessaire de placer un point d'arret. Nous allons placer un point d'arret sur la fonction main :
(gdb) break main
>	break main
Breakpoint 1 at 0x8048517: file ex.c, line 29
(gdb)
Le système nous confirme que le point d'arret est bien positionné ? On constate aussi qu'il sait parfaitement où est la fonction main dans le programme source, grace à la table des symboles. Recommençons maintenant l'exécution :
(gdb) run
>	Starting program: a.out

Breakpoint 1, main () at ex.c:29
29        f (10);
(gdb)
Cette fois-ci, le syst?me nous indique qu'il a rencontré un point d'arret dans le fichier ex.c, ligne 29.
Il est possible de placer des points d'arret par rapport à une position dans un fichier et de placer une condition ou un compteur.
Pour afficher la liste des points d'arret, on utilise la commande break sans arguments :
(gdb) break
>	Breakpoint 2 at 0x8048476: file ex.c, line 11.
(gdb)
Pour effacer l'ensemble des points d'arret, utiliser la commande delete. Pour effacer le point d'arret où le système est actuellement positionner, utiliser clear sans arguments.
Ex?cution pas ? pas
Nous sommes donc arrétés sur le point d'arret de la fonction main. Nous voulons afficher le les lignes de code qui entourent la position courante :
(gdb) list
>	24        else           return  f (x / 3.0);
25      }
26
27      /* programme principal */
28      void main (void) {
29        f (10);
30      }
(gdb)
Cela permet souvent de savoir où on en est !
Bon, avançons d'une ligne :
(gdb) next
>	a = 10.000000
x = 20.000000
...
a = 0.006766
(gdb)
Ah, on est allé trop vite ! En effet, la commande next fait avancer d'un pas, sans entrer dans les fonctions. Pour entrer dans les fonctions, il faut utiliser la commande step. Exécutons à nouveau le programme :
(gdb) run
>	The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/tmp/a.out

Breakpoint 1, main () at ex.c:29
29        f (10); 
(gdb)
Nous voyons que la session de debogage precedente n'est pas terminee. Mais gdb supporte parfaitement le redémarrage. Un point important : le programme est exécuté exactement au meme emplacement en m?moire. Cela permet d'utiliser des références en mémoire absolues, lorsque c'est nécessaire.
Maintenant, utilisons la commande step pour entrer dans la fonction f au lieu de l'exécuter :
(gdb) step
>	f (a=10) at ex.c:11
11        printf ("a = %f\n", a);
(gdb)
Maintenant, nous sommes à l'intérieur de la fonction f. Nous pouvons poursuivre le débogage à l'aide de next, step et run, autant de fois que nous le souhaitons. Notons que les commandes on une syntaxe courte. Par exemple, la commande step peut etre activée avec la lettre s seule, next avec n et run avec r.
Cadre
En tout point de notre exécution en mode pas à pas, le débogueur garde une trace des appels de fonctions précédents. Chaque appel est stocké dans le débogueur dans un cadre d'exécution (execution frame). Pour afficher les cadres, utilisons bt (backtrack)?:
(gdb) bt
>	#0  f (a=10) at ex.c:11
#1  0x8048523 in main () at ex.c:29
(gdb)
Cet affichage nous indique la liste des fonctions qui sont en cours d'exécution, par rapport à l'endroit du programme où nous sommes.
Gdb permet de circuler parmi les cadres, avec les commandes up et down. La première ``remonte'' d'un cadre (vers main, dans notre cas) et la seconde ``descend'' d'un cadre :
(gdb) up
>	#1  0x8048523 in main () at ex.c:29
29        f (10);
(gdb)
Nous sommes maintenant dans le cadre de main. Nous pouvons examiner les variables, les modifier, puis nous revenons dans le cadre de f avec :
(gdb) down
>	#0  f (a=10) at ex.c:11
11        printf ("a = %f\n", a);
(gdb)
Affichage
Il est aussi très utile d'afficher le contenu d'une variable. Pour cela, on utilise la commande print :
(gdb) print a
>	$1 = 10
(gdb)
Il est possible de modifier le format d'affichage :
(gdb) print/x a
>	$1 = 0xa
(gdb)
Cette dernière commande affiche la variable a en hexadécimal. On peut utiliser les types c (caractère), x (hexadécimal), o (octal), d (décimal).
Affichage permanent
Parfois, on souhaite afficher en permanence une variable, à chaque étape de l'exécution. Pour cela, on utilise la commande display :
(gdb) display a
>	1: a = 10
(gdb)
Maintenant, à chaque pas de l'exécution, la valeur de a sera affichée. Gdb n'affiche les variables de la sorte que aux endroits du programme où elles sont accessibles.
Aide et documentation
Gdb dispose d'une aide en ligne, pas très facile à utiliser. Elle est invoquée avec :
(gdb) help
>	List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
...
Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
Une aide plus complète est plus facile d'accès est disponible dans l'éditeur emacs. Nous préférons utiliser xemacs, plus ergonomique.
XEMACS
Aide en ligne
La première chose à connaitre dans emacs est la manière d'appeler l'aide. Une fois l'éditeur lancé, taper sur la touche [ECHAP] puis sur la touche [X] : cela active la ligne d'édition en bas de l'écran. Taper alors info puis [ENTER]. Nous avons alors l'écran suivant :

Fig 1?: Aide hypertexte de emacs.
Une fois dans l'aide hypertexte, rechercher la chaine de caractères gdb (en tapant [CTRL]+[S], puis gdb), se placer au début de la ligne et presser [ENTER] : Nous sommes maintenant dans l'aide de gdb, très complète.
Cette aide est très utile pour compléter ses connaissances de gdb. Elle traite aussi de nombreux autres sujets, comme emacs lui-meme, les fonctions C (libc), la gestion des projets (make), la gestion de version (Source Control), etc.
Ouvrir le fichier
Chargeons maintenant le fichier ex.c dans emacs en tapant [CTRL]+[F] ou en ouvrant le menu File->Open. Lorsque l'on utilise les touches, la touche [TAB] sert à compléter la ligne avec les choix possibles. Le caractère ~ est remplacé par le nom du répertoire de l'utilisateur.
Lorsque le fichier est chargé, on constate avec bonheur que emacs reconnait la syntaxe du C et colorise le fichier.
Dans emacs, en mode édition, la touche [TAB] n'insère pas de tabulation, mais indente la ligne correctement, en fonction du contexte?: C'est très pratique pour écrire des programmes toujours impécables !
Compilation
Pour compiler un programme à l'intérieur de emacs, rien de plus simple : Appuyer sur le bouton [Compile] de la barre d'outils, puis sur le bouton [Edit?Command].
En base de l'écran, taper alors la commande de compilation utilisée précédemment, gcc -g ex.c. Emacs ouvre un second cadre contenant le résultat de la compilation.
Dans la figure suivante, nous avons volontairement provoqué une erreur en ajoutant la seconde ligne :

Fig 2?: Compilation dans emacs.
En cliquant sur le message d'erreur, emacs nous place directement dans le fichier source ayant provoqu? l'erreur, à la bonne ligne. Pas mal, non ?
Avec la commande de compilation make, emacs permet de compiler des projets qui peuvent etre volumineux. Il est aussi possible de gérer les versions des sources (regardez à Version Control dans l'aide info).
En fait, emacs se comporte comme un véritable environnement de programmation. Mais sa force vient du fait qu'il est capable de lancer n'importe quelle commande de compilation, et dont de compiler n'importe quoi.
Si vous l'avez ajoutée, enlevez la seconde ligne et recompilez votre programme.
Déboguer
Maintenant, nous allons déboguer le programme sans quitter emacs ! Pour cela, cliquer sur le bouton [Debug] de la barre d'outils. La boite de dialogue qui s'ouvre demande le nom du binaire à déboguer : Le binaire s'appelle a.out. 
Et voila, nous sommes dans le débogueur de emacs, comme on le voit dans la figure suivante :

L'auteur
Guilhem de Wailly, directeur de la société Erian Concept, partenaire RedHat France, Khéops et Getek:

	Support, formations, configurations, administration, développements Linux.
http://www.erian-concept.com	

Références
Langage C - norme ANSI 
Ph. DRIX
Masson
Programmer en C++ 
S.C. DEWHURST et K.T. STARK
Masson
Le Langage C
B.W. KERNIGHAN et D.M. RITCHIE 
Masson