Sur la compilation séparée séance du 12 octobre 2009 1. la compilation comporte plusieurs phases Au début on fait g++ prog.cc -o prog pour fabriquer l'éxécutable "prog" à partir du source "prog.cc". En fait on peut décomposer en deux étapes a- la compilation proprement dite (analyse du source et production de code) g++ -c proc.cc qui fournit un "module objet" nommé "proc.o" b- l'édition des liens, qui combine le module objet à la bibliothèque standard C++ g++ proc.o -o prog 2. Compilation séparée L'édition des liens permet de reéunir plusieurs bibliothèques et modules objets. On a donc l'idée de décomposer un "gros" programme source en plusieurs morceaux que l'on va compiler séparément, en fabriquant autant de modules objets. Intérêt : - on évite de recompiler tout le source à chaque modification - le même module objet peut être utilisé par plusieurs programmes. 3. Exemple : découpage d'un source On part de l'exemple suivant, dans lequel on souhaite détacher une fonction fac (calcul de factorielle), pour la mettre dans un module d'"utilitaires" utilisable dans plusieurs programmes. <<<< // prog.cc #include using namespace std; int fac(int n) { int r; r = 1; for (int i=1; i<=n ; i++) r *= i; return r; } int main() { cout << "factorielle 5 = " << fac(5) << endl; } >>>> a - le source "utilitaire.cc" va comporter la fonction factorielle (et d'autres ensuite) <<<< // utilitaire.cc int fac(int n) { int r; r = 1; for (int i=1; i<=n ; i++) r *= i; return r; } >>>> La compilation ne pose pas de problème $ g++ -c utilitaire.cc $ ls -l utilitaire.* -rw-r--r-- 1 billaud profs 107 oct 13 15:13 utilitaire.cc -rw-r--r-- 1 billaud profs 715 oct 13 15:14 utilitaire.o b - le source prog.cc Par contre si on compile le reste du source du programme principal (en effaçant fac), ça se passe mal <<<< #include using namespace std; // fonction fac supprimée int main() { cout << "factorielle 5 = " << fac(5) << endl; } >>>> $ g++ -c prog.cc prog.cc: In function 'int main()': prog.cc:7: error: 'fac' was not declared in this scope parce que la fonction fac(), qui est utilisée, est inconnue. Pour que le compilateur l'accepte, il faut que le prototype de fac soit connu lors de la compilation du programme principal. Le prototype : c'est l'entete de la fonction, avec le type du résultat et des paramètres. Une premiere idée serait d'insérer dans le source de prog.cc une ligne int fac(int n); mais si le module comportait plusieurs fonctions ce serait fastidieux de recopier tous les prototypes Une meilleure idée est d'inclire un fichier "utilitaire.h" contenant tous les prototypes <<<< #include #include "utilitaire.h" using namespace std; int main() { cout << "factorielle 5 = " << fac(5) << endl; } >>>> c- les entetes le fichier d'entete est le suivant <<< // utilitaire.h int fac(int n); >>> Les compilations se passent bien : $ g++ -c utilitaire.cc $ g++ -c prog.cc $ g++ prog.o utilitaire.o -o prog $ ./prog factorielle 5 = 120 4. Une précaution Une précaution est d'inclure aussi le fichier d'entetes dans le source du module <<<< // utilitaire.cc #include "utilitaire.h" int fac(int n) { int r; r = 1; for (int i=1; i<=n ; i++) r *= i; return r; } >>>> On pourra alors compter sur l'aide du compilateur pour signaler d'éventuelles incohérences entre les fonctions définies dans le module et celles qui sont citées dans le fichier d'entete. 5. Supplément : éviter les inclusions multiples Dans la vraie vie, les programmes principaux font appel à des modules qui font eux même appels à des modules etc. Rapidement, on ne sait plus sait plus qui doit inclure quoi. Pratique usuelle : on utilise donc une variable du préprocesseur comme drapeau indiquant que l'entete a déjà été inclus. Cette variable reflètera le nom du fichier entête, et sera définie lors de la première inclusion. <<< // utilitaire.h #ifndef _UTILITAIRE_H_ #define _UTILITAIRE_H_ int fac(int n); #endif >>> 6. Anticipons : Makefile Quand un programme comporte plusieurs fichiers sources, il est difficile, au bout d'un moment (très bref) de savoir ce qui doit être re-fabriqué après la modification d'un des sources. Ici : après modif de utilitaire.h => reconstruire utilitaire.o et prog.o utilitaire.cc => reconstruire utilitaire.o prog.cc => reconstruire prog.o et bien sur, si on a reconstruit un module objet, il faut refaire l'exécutable. Plus tard, vous verrez la commande make qui permet de gérer les recompilations. Un fichier "Makefile" indique, pour chaque "cible" (module objet ou exécutable), les fichiers dont il dépend, et la commande qui permet de les reconstruire <<< # Makefile prog: prog.o utilitaire.o g++ -o prog prog.o utilitaire.o prog.o: prog.cc utilitaire.h g++ -Wall -c prog.cc utilitaire.o: utilitaire.cc utilitaire.h g++ -Wall -c utilitaire.cc >>> En lançant "make prog" (ou "make" tout court), les compilations nécessaires - et suffisantes - sont effectuées.