séparation en fichiers .c et .h et Makefile

La découpe en fichier.

Quand on commence à écrire de grand programmes C, il est utile de découper son programme en plusieurs fichiers. En effet certaines parties sont communes à plusieurs applications. Par exemple dans les programmes de calculs sur le calendrier, la transformation d'un jour en jour Julien va être utilisée par plusieurs programmes. Il est donc intéressant de mettre les fonctions dans un même fichier.

On peut imaginer deux stratégies pour intégrer ces fonctions au programme principal.

demande de compiler le programme principal.c avec le compilateur gcc, puis de faire le lien avec les fichiers fonction et fonction2 et de créer un exécutable appelé principal. Bien entendu la fonction main doit être uniquement définie dans le fichier principal.c.

Pour voir les fonctions comprises dans un fichier objet on peut utiliser la commande Unix nm. Par exemple

fichier : Principal.c



fichier : fonction.c


fichier : fonction2.c


Le programme principal affiche une chaîne de caractères puis appelle la fonction fct1 qui appelle à son tour la fonction fct2. Ces deux fonctions affichent une chaîne de caractères. On compile les fichiers fonction.c et fonction2.c avec l'option -c pour obtenir deux fichiers objets. La commande nm va alors donner le résultat suivant (Voir le man pour savoir à quoi correspondent les lettres) :
Cet objet contient deux variables qui indiquent que l'objet à été créé avec le compilateur gcc (gnu). Ce programme contient une fonction qui a été définie. Il s'agit de fct1 et deux fonctions qui ont été utilisées sans être définies (fct2 et printf).

Le fichier objet fonction2.o est à peu près identique :

Le fichier objet de l'exécutable principal contient :

On retrouve dans ce fichier les appels des fonctions main, fct1 et fct2. L'appel à la fonction printf n'apparaît pas dans ce listing car l'édition utilise des liens dynamiques comme nous le verrons au chapitre suivant.


Quand les programmes sont séparés en plusieurs fichiers, il existe quand même des définitions qui doivent être partagées (définition des structures, constantes, defines, fonctions, ...). Celles-ci sont regroupées dans des fichiers d'include .h. La composition d'un programme est définie dans la figure suivante.

La compilation conditionnelle: Le fichier Makefile.

La découpe en fichiers du programme permet de ne recompiler que la partie modifiée. Dans l'exemple présenté dans la figure précédente si l'on modifie le fichier pgm.c, pour obtenir un bon résultat, il suffit de relancer la commande gcc pgm.c f1.o -o pgm.

Si le fichier f1.c est modifié, il faut recompiler le fichier f1.c pour créer un nouveau fichier objet puis recompiler le programme principal. De même si le fichier f.h est changé. Il est plus adapté de passer par une étape supplémentaire en transformant tous les programmes en fichiers objets puis en lançant l'édition de lien.

Si l'on pouvait mettre dans un fichier les règles de dépendance d'un programme (i.e de quels autres fichiers dépend un fichier) la phase de recompilation serait automatique. Le fichier Makefile permet de définir ces règles. La syntaxe du fichier est très simple :

<nom du fichier que l'on définit> : <fichier dont il dépend> <fichier dont il dépend> ... tabulation <règle pour obtenir le fichier> Attention de bien commencer la ligne de commande par une tabulation, une série de caractères espaces ne marche pas

On commence par le résultat que l'on veut obtenir, c'est-à-dire par le fichier pgm. Ce fichier dépend de f1.o et de pgm.o. Si l'un des deux est modifié, il faut relancer l'édition de liens (gcc pgm.c f1.o -o pgm). Dans le langage du Makefile cela se traduit par :

pgm: pgm.o f1.o
tabulation	gcc pgm.o f1.o -o pgm
Il faut maintenant définir les règles de dépendance pour les fichier pgm.o et f1.o.
pgm.o: pgm.c f.h
tabulation	gcc -c pgm.c 
f1.o: f1.c f.h
tabulation	gcc -c f1.c
Pour lancer une compilation ou une recompilation, il faut taper make. Cette commande ira lire par défaut le fichier Makefile et exécutera les commandes définies.

Les bibliothèques

Si un programme comporte beaucoup de fonctions, la manipulation de fichiers objets peut s'avérer fastidieuse si les fonctions sont placées dans des fichiers différents. Pour ne manipuler qu'un seul fichier on peut les regrouper en archive. on crée une archive ou on ajoute un fichier dans une archive en tapant la commande :
ar r <nom de l'archive> <fichier objet à inclure>
Quand la construction de l'archive est terminée, il faut créer une table des fonctions qu'elle contient en tapant la commande
ranlib <nom de l'archive>
Une archive peut avoir n'importe quel nom. Mais le compilateur aime bien le nom libxxxxx.a. En effet lors de la compilation on pourra taper :
gcc toto.c -lxxxxx
Le compilateur ira chercher dans différents répertoires pour voir s'il trouve le fichier libxxxxx.a pour faire l'édition de liens. On peut demander au compilateur de rechercher dans d'autre répertoires en précisant leur nom après l'option -L.
gcc toto.c -lsamson -L/home/permanents/samson/v1.1.1
demande au compilateur de faire l'édition de liens avec la bibliothèque libsamson.a qui se trouve dans /home/permanents/samson/v1.1.1. En général les bibliothèques standards se trouvent dans la directory /usr/lib.


Il existe deux types de bibliothèques :

En principe, les bibliothèques que vous aller créer seront statiques et les bibliothèques du système seront dynamiques.

Le debogueur

Le debogueur est un outil vital pour le programmeur. Il ne suffit pas d'écrire un programme qui compile, il faut écrire un programme qui marche. Si les erreurs de compilation sont facilement corrigeables (il suffit de modifier le programme jusqu'à ce que ça marche), les erreurs logiques sont beaucoup plus vicieuses. Le C n'arrange rien, il est beaucoup trop permissif.

Il existe beaucoup de débogueurs, celui qui a été choisi est gdb car vous le retrouverez aussi bien sur les stations de travail HP que sun. Il permet de travailler avec le compilateur gcc. Dans la suite de votre scolarité vous pourrez aussi l'utiliser avec le C++.

Le manuel en ligne de gdb est sommaire voire inexistant, vous trouverez donc dans la salle des manuels papier.

Il existe des applications X-window qui s'interfacent avec gdb. Si elles sont attrayantes au début, elle sont vite décevantes. Nous avons donc choisi de vous présenter l'interfaçage de gdb sous emacs. Si les commandes d'emacs vous semblent ésoteriques, ce n'est pas un investissement perdu. Emacs se retrouve sur presque tous les systèmes d'exploitation et les commandes sont généralement utilisées comme raccourcis clavier des applications x-window.

Pour pouvoir utiliser gdb sur un programme, il faut utiliser l'option -g lors de la compilation. Cela rajoute au fichier objet une base de données qui permettra le déboguage symbolique. Si cette option n'est pas précisée, vous devrez déboguer de l'assembleur.

Sous emacs pour lancer gdb il faut taper M-x gdb <return>. Dans la petite fenêtre apparaît le chemin de la directory où vous êtes. Vous tapez le nom de l'exécutable que vous voulez déboguer.

Dans la grande fenêtre vous allez avoir quelque chose qui ressemble à :

Current directory is /home/toutain/toutain/docu/enstb/ex_c/exemples/
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "info copying" to see the conditions.
There is absolutely no warranty for GDB; type "info warranty" for details.
GDB 4.1, Copyright 1991 Free Software Foundation, Inc...
(gdb) 
Après l'invite (gdb) vous pouvez taper vos commandes. Par exemple br main qui indique que vous voulez arrêter le programme dès le début, c'est-à-dire quand la fonction main est invoquée. Vous aurez en réponse un message du genre :
Breakpoint 1, main () at principal.c:3
qui indique qu'un point d'arrêt (breakpoint) a été mis ligne 3 dans la fonction main qui se trouve dans le fichier principal.c.

Vous lancez l'exécution du programme en tapant run. Si le programme nécessite des arguments vous les donnez à la suite.

Si tout ce passe correctement la fenêtre se divise en deux. Dans l'écran du bas apparaît le source du programme avec un symbole => qui indique la ligne qui va être exécutée.

Le tableau suivant regroupe les principales commandes dont vous aurez besoin.

instructions effet
n ou next permet d'exécuter une ligne du programme source. Si cette ligne contient un appel de fonction déboguable, gdb reste au niveau de la fonction.
s ou step permet d'exécuter une ligne du programme source. Si cette ligne contient un appel de fonction déboguable, gdb entre dans cette fonction.
finish va jusqu'au bout de l'appel de fonction.
cont continue l'exécution du programme jusqu'au prochain point d'arrêt rencontré.
br <fonct> met un point d'arrêt au début de la fonction fonct
^xespace cette instruction se tape dans la fenêtre où se trouve le listing du programme. Elle permet de placer un point d'arrêt à la ligne en cours.
i b donne tous les points d'arrêt placés dans le programme
d x y z efface les points d'arrêt numéro x y et z. Les numéros sont donnés par la commande précédente.
p ou print permet d'afficher le contenu d'une variable. La syntaxe est la même qu'en C.
p toto affiche le contenu de la variable toto
p *toto affiche le contenu pointé par toto
p (struct sockaddr) *toto permet d'afficher le contenu pointé par toto suivant le format défini par la structure sockaddr.
display Cette instruction a la même syntaxe que le print, mais elle sera exécutée à chaque fois que l'utilisateur retrouvera la main.
call permet d'exécuter une fonction du programme débogué. Par exemple call time(0).
where permet d'afficher la pile du programme (i.e. tous les appels de fonctions imbriqués avec les valeurs des paramètres).
do permet de descendre dans la pile.
up permet de remonter dans la pile.


Sommaire