Bonjour,
Je reviens différemment sur ce thème, déjà évoqué dans un précédent fil: http:[i]http://www.developpez.net/forums/d12...multi-modules/. Désolé pour ce message très long, mais j'essaie de faire le tour de la question en procédant logiquement, dans la mesure de mes moyens, et en abordant les divers aspects souvent laissés implicites du sujet, ainsi que les liens (sic!) entre ces aspects.
J'essaie de comprendre:
1. Les rôles respectifs des fichiers sources .c et .h .
2. Les rôles respectifs de l'inclusion (#include) et du "liage" explicite.
Je veux par ce dernier point l'indication explicite d'un nom de fichier (compilé) sur la ligne de commande d'édition de lien. Ce que je voudrais, au-delà de "comment faire?", arriver à comprendre assez clairement comment ça marche.
Pour ça j'utilise le programme exemple suivant:
Exploration:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 // prog.c #include "salut.c" [i] cette ligne-là change ******* int main () { salut () ; return 1 ; } // salut.c #include <stdio.h> void salut () { puts ("Salut au monde !") ; } // salut.h void salut () ;
* Sans la ligne Astérix (;-), pas de compil.
* En incluant l'un ou l'autre salut.*, la compil roule.
* En incluant salut.h seul, le liage plante.
* En incluant salut.c seul, le liage et l'exécution roulent.
* Inclure .c et .h à la fois ne change rien: ça roule.
Alors l'édition de lien, là je la fais sans indication d'autre fichier que prog.o:
gcc -Wall -o prog prog.c
Déjà, je viens de réaliser réellement que l'incluson "physique" (sic!) des fichiers fait que le compilateur sait déjà presque tout sur la totalité de l'appli (sauf s'il y a incohérence entre une déclaration dans un fichier et la définiton correspondante ailleurs, ou son absence). Mais alors, que rajoute la distinction entre .c et .h? et celle entre compilation et édition de liens? Là, je pige plus trop.
J'ai dans l'idée que les deux marchent ensemble, et qu'ils ne servent qu'à faire de la compilation séparée. Les .h auraient pour but d'empêcher le compilateur de râler lorsqu'on compile séparément un module qui utilise des fonctionnalités externes (tiens, à propos, extern, encore un sujet à comprendre), en lui disant en gros "fais moi confiance, ce truc-là existe et il est comme j'te dis".
Très bien mais pour moi, sur une machine moderne, tant que le projet ne dépasse pas une certaine échelle, ça ne sert pas à grand chose. (Surtout que le compilo ne va pas recompiler les fichiers inchangés.) Et encore moins en phase de développement où l'optimisation (ce qui bouffe l'essentiel du temps de compil) est plus que secondaire.
Le truc, donc, ce serait de mettre les déclarations uniquement dans le .h, et de compiler en incluant seulement cela. Ce qui allège et accélère le processus. Ensuite, au liage, au lieu d'inclure les .c (ce qui imposerait une recompilation), on les indique à l'éditeur de lien explicitement. Est-ce que mon raisonnement est correct?
[HS: y a pas des mots français pour link, linker, linking? Pourquoi lier, lieur, liage (ou liaison ;-) ne sont-ils pas utilisés? On pourrait aussi dire re-lieur, re-lier rel-liage.]
Pour en revenir à mes problèmes, il me semble qu'il n'y a pas de différence fonctionnelle entre l'inclusion à la compilation et le "reliage" a posteriori. Je veux dire par là que mon prog fonctionnera strictement pareil si j'inclue le .c (avec ou sans .h) ou si j'inclue le .h seul et "relie" ensuite le .c. Est-ce que j'ai raison?
Je vérifie que ça marche, au moins (compil avec .h seul):
Alors est-ce que cette ligne de raisonnement est valable quelle que soit l'architecture du projet: je peux toujours soit inclure les .c (les fichiers qui contiennent les définitions explicites) (avec les .h si les déclarations sont exportées) à la compilation, soit inclure les .h (les fichiers qui contiennent les définitions explicites) uniquement et relier les .c à l'édition de lien seulement. Correct?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 spir@ospir:~/prog/C/comp$ gcc -Wall -o prog salut.o prog.o spir@ospir:~/prog/C/comp$ ./prog Salut au monde !
J'ai encore un petit problème. Je modifie mon programme ainsi, ce qui exporte la définition de 'salutation':
Là, malgré le #ifndef dans salut.h, j'ai une erreur à l'édition de lien (qui comme plus haut indique salut.o explicitement):
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 // prog.c #include "salut.h" int main () { salut () ; return 1 ; } // salut.c #include <stdio.h> #include "salut.h" void salut () { puts (salutation) ; } // salut.h #ifndef H_SALUT #define H_SALUT const char * salutation = "Salut au monde !"; void salut () ; #endif
C'est tout-à-fait logique si je comprends bien: du fait de la compilation séparée, prog.c et salut.c incluent tous deux salut.h, malgré la précaution du #ifndef. Correct? Mais je suis bien obligé de l'inclure dans chacun, pour compiler, non? Je peux aussi dans prog.c inclure salut.c au lieu de salut .h, mais c'est pas ça le but du jeu, car là je compile avec prog.c la totalité du programme, ce n'est plus de la compilation séparée en fait. Alors quelle est la méthode que je n'ai pas trouvée tout seul?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 spir@ospir:~/prog/C/comp$ gcc -Wall -o prog salut.o prog.o prog.o:(.data+0x0): multiple definition of `salutation' salut.o:(.data+0x0): first defined here
Le but à terme pour moi est de définir la manière la plus pratique d'utiliser les outils C avec mon style de programmation "exploratoire". Ce style, en gros, du point de vue compilation, c'est que chaque fichier source peut être lancé à tout moment (en fonction de là où j'en suis dans mon développement) pour tester ses fonctionnalités ou son comportement exact, ou encore pour diagnostic. Typiquement (c'est une vision schématique), chacun de mes fichiers se termine par qq ch du genre:
Il me semble que les outils genre makefile sont faits pour tout autre chose. En gros, pour moi, ils serviraient plutôt à distribuer le code en préparant la compilation par l'usager (utilisateur final ou autre programmeur) de l'application complète (ou du composant logiciel complet). Mais je me trompe peut-être totalement, peut-être que les makefiles sont tout-à-fait capables de permettre de programmer à ma façon.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 void test_xxx () { ... } void test_yyy () { ... } void test_zzz () { ... } void test () { // "hors-commenter" ce que je ne teste pas là maintenant test_xxx () ; test_yyy () ; test_zzz : } // "hors-commenter" pour inclusion par un autre fichier source void main () { test () ; exit (1) ; }
Sinon, je suis prêt à écrire des outils moi-même. Par exemple, un bout de code qui lirait les #include dans les en-têtes de mes fichiers source, récursivement, en les cherchant dans une arborescence locale, pour éditer un fichier batch de compilation / édition de liens / exécution test.
Quelle méthode recommanderiez-vous?
2 autres points que j'ai en-tête d'explorer sont:
* Les différents type de fichiers à inclure prédéfinis par C ou par les outils C, et notamment pourquoi/comment l'éditeur de liens trouve les libs standards, et le sens concret de cette différence de syntaxe d'inclusion (<...> ou "...").
* Le(s) bon(s) usage(s) d'un makefile.
PS: J'ai dans l'idée de retracer ce fil de discussion pour un tutoriel sur le(s) sujet(s). Qu'en pensez-vous?
Merci à tous,
Denis
Partager