La programmation modulaire

Avatar de Monsieur X
Description du chapitre et des ses objectifs :

Enfin, voilà la clef de tout projet d'envergure, la programmation modulaire. Ici vous apprendrez à séparer votre code sur plusieurs fichier afin de le réutiliser plus facilement et de mieux vous y retrouver. N'espérez pas pouvoir travailler correctement en groupe sans lire ce chapitre!

Image de Partie

Accéder directement à une des parties du cours :

Le principe

Afin d'organiser son code, il est possible d'ecrire son code sur plusieurs fichiers .c différents. Ces fichiers .c peuvent avoir une interface qui permet à d'autres fichiers .c d'utiliser le code contenu dans le .c initial. Chaque fichier .c appellé par un autre .c doit donc avoir ce que l'on appelle un fichier d'en-tête (ou header) avec le même nom que le .c.

Par exemple, si vous avez un fichier calcul.c et que vous souhaitez utiliser les fonctions de calcul.c dans votre main.c, il faudra créer un fichier calcul.h qui contiendra tous les prototypes de calcul.c. On dit alors que main.c à accès, par l'intermédiaire de l'interface calcul.h, aux fonctions de calcul.c .

Dans la pratique, on inclus les headers par l'intermediaire de la directive préprocesseur #include. Vous avez déjà rencontré cette méthode avec

  1. #include <stdio.h>
par exemple. Dans ce cas, les <> signifie que le .h se situe dans un répertoire par défaut connu du compilateur.
Lorsque vous créez un projet, et que vous mettez un .h dans votre répertoire courant; pour avoir accès à ses déclarations, il faudra respecter la syntaxe suivante:
  1. #include "<nom>.h"
Le .h sera alors cherché dans le répertoire courrant.
Vous pouvez aussi aller chercher un .h dans un autre répertoire en idiquant le chemin relatif du .h en question. Par exemple, s'il est situé dans un dossier accessible à partir du dossier parent, vous faites:
  1. #include "../<nom_dossier>/<nom>.h" // .. signifiant dossier parent


Note sur la compilation:
La compilation s'effectue en 3 étapes:
- D'abord intervient le préprocesseur avec toutes les directives que l'on a vu, il va s'occuper donc d'inclure toutes les déclarations faites dans vos différents .h
- Puis le compilateur en lui même intervient. Sa tâche est un peu spécifique, il va transformer chacun des .c en un fichier .o équivalent. Le .o est un état intermédiaire binaire. Si vous compilez "à la main" sous Linux par exemple, vous pourrez plus facilement intervenir à ce moment là. Je vous conseille d'ailleurs de chercher un cours sur les Makefile qui automatiseront la compilation.
- Enfin la dernière étape qui consiste à transformer chacun des .o en executable à part entière s'effectue par l'éditeur de lien. Il aura par la même occasion le rôle d'aller chercher chacune des bibliothèques dont vous avez besoin afin d'arriver au fichier binaire final.

Attention : Les .o sont spécifiques à chaque compileur! N'essayez pas de passer un .o créé par mingw32 au compàilateur de Visual-C++ par exemple!

Sous Visual-C++

L'ajout de nouveaux fichiers dans un projet dans les IDE est facile et se passe de la même façon. Il suffit de faire un click droit sur le projet, selectionner ajouter, et dans la partie qui s'affiche, choisissez si c'est un nouvel élément que vous créez, on un élément déjà fait que vous voulez intégrer à votre projet.
Si vous créez un nouveau fichier, sélectionnez l'icone Fichier C++(.cpp) et donnez lui un nom quelquonque mais finissant par .c pour rester dans un projet C et non C++, exemple: fonctions_pomme.c
Un nouvel onglet devrait se créer contenant la page de code de fonctions_pomme.c. Pour l'instant cette page est vierge.
On ne va pas oublier de mettre un .h correspondant au .c que l'on viens de créer. Direction l'ajout de nouvel élément que l'on vient d'utiliser. Choisissons maintenant le .h et donnons lui le nom de: fonctions_pomme.h. Encore un nouvel onglet devrait s'ouvrir, qui contient lui le code du .h .

Information : Vous pouvez sélectionner et modifier rapidement vos fichiers contenu dans votre projet en utilisant la fenêtre de l'explorateur de solutions. Comme vous pouvez le remarquer, les .h que l'on crée vont dans le filtre fichiers d'en-tête et les .c dans Fichiers source. Si vous souhaitez organiser différemment, vous pouvez aller dans ajouter/filtre.


Pour les autres IDE, c'est globalement la même chose, click droit sur le projet puis ajouter...
Si vous êtes sous Linux, de base, il vous suffira de mettre le nom de tous les fichiers concernés sur la ligne d'execution du compilateur.

Sous Code::Blocks

A faire :[

Explications de cas possibles

Voyons un peu comment ça va se passer.

En résumé, ce qu'il va donc ce passer au moment de la compilation, ce sont les directives préprocesseur qui vont commencer par copier le code des .h dans les .c qui les ont inclus.
Après cette étape, on se retrouvera donc avec en haut des .c les prototypes et toutes autres déclarations. L'éditeur de lien se chargera alors de faire le lien avec les fonctions écrites dans les différents fichiers convertis alors en .o.

Imaginons les fichiers suivants:
main.c, fichier1.c, fichier2.c, fichier1.h et fichier2.h

Si dans le main.c je retrouve la directive #include "fichier1.h" on aura alors accès depuis le main.c à tout ce qui a été déclaré dans le fichier1.h.
Supposons maintenant que ce fichier fichier1.h possède la directive #include "fichier2.h", le fichier1.h aurait alors accès à toutes les déclarations de fichier2.h, toutes les déclarations seraient alors copiées dans le fichier1.h d'où elles seront exploitables depuis le main.c (et le fichier1.c s'il possède l'instruction #include "fichier1.h").

Cependant, si l'on trouve la configuration suivante:
Le main.c possède #include "fichier1.h"
Le fichier1.c possède #include "fichier2.h"
Alors, malgré le fait que le fichier1.c a la possibilitée d'utiliser les déclarations de fichier2.h, le main.c n'aura alors aucunement accès aux déclarations de fichier2.h. Les déclarations dans un fichier source (.c) ne vont donc pas plus loin que lui-même.

Il existe aussi un cas dans le quel un fichier.h inclus un autre .h et vice-versa. Ce cas la engendre une boucle infinie au moment de la compilation. Pour éviter celà (la multi-inclusion) il existe une technique de protection grace aux directives préprocesseur. En effet dans chaque .h, il faudra veiller à mettre:

  1. #ifndef FICHIER_H // si ça n'a pas été définit, remplacez FICHIER_H par le nom de votre fichier header (.h), il ne faut définir que un seul nom de constante par fichier .h au risque de voir certains fichiers non inclus.
  2. #define FICHIER_H // On le définit, on ne va donc lire ce qui va être déclaré une seule fois.
  3.  
  4.  
  5. /* tout ce que vous écrivez ici sera alors protégé de la multi-inclusion
  6. Ecrivez donc toutes vos déclarations entre ces instructions. */
  7.  
  8.  
  9. #endif // fin de la protection
.

Vous voici protégé de toute maladresse. Il existe d'autres méthodes, mais celle ci semble être la plus pratiquée et portable.


Et vous voilà parré pour la création d'un beau projet! Grâce à cela vous pourrez de plus en plus faciliter la gestion d'un projet en groupe, et faciliter drastiquement la réutilisabilitée de votre code.

Chapitre précédent - Sommaire - Chapitre suivant

Nos rédacteurs et membres sont pour la plupart ouverts à des remarques constructives et servir à alerter le rédacteur du cours, des fautes éventuelles ou de propositions et nouvelles perspectives de cours etc ...
Pour ce faire cliquez ici

Postez vous aussi un commentaire à cette partie via le lien que voici