Bonsoir,
Je me pose une petite question sur la compilation statique.
Quand je compile avec 1 bibliothèques, est-ce la bibliothèque complète qui est liée à mon code ou juste les fonctions de celles-ci que j'utilise;
Merci pour votre réponse.
Version imprimable
Bonsoir,
Je me pose une petite question sur la compilation statique.
Quand je compile avec 1 bibliothèques, est-ce la bibliothèque complète qui est liée à mon code ou juste les fonctions de celles-ci que j'utilise;
Merci pour votre réponse.
Salut
Question que je ne m'étais jamais posé. A priori je dirais qu'il s'agit de la bibliothèque intégrale. Je pense que c'est ça parce qu'une bibliothèque possède un index sur ses fonctions permettant un accès plus rapide. Et si c'était juste le code de la fonction qui était intégré dans l'exe, cela rendrait cet index inutile...
Bonjour,
Oui, c'est la bibliothèque entière qui est liée à ton programme. Intégrer uniquement les ressources réellement utilisées serait beaucoup plus compliqué que ce que l'on pourrait imaginer car cela demanderait d'une part des infrastructures spéciales permettant d'isoler, d'extraire et de reloger les ressources exploitées (c'est-à-dire pratiquement recompiler la bibliothèque) et en outre, il serait algorithmiquement très compliqué d'affirmer de manière déterministe si le code va réellement être utilisé ou pas.
Même en considérant qu'à partir du moment où ton programme fait référence à une ressource de ta bibliothèque, celle-ci est considérée comme utilisée, il faudrait passer la bibliothèque elle-même aux rayons X pour affirmer que ses fonctions ne se feront pas références entre elles.
À dire vrai, le problème se pose également avec les bibliothèques dynamiques : à partir du moment où on en a besoin, celles-ci seront chargées intégralement en mémoire. Cela dit, le mécanisme de pagination qui gère en même temps la mémoire swap fera que seules les pages réellement lues à un instant donné seront chargées dans les faits. Dans le principe, ça semble être la solution au problème. En pratique, ce sera vrai mais seulement avec la granularité d'une page.
Au moins pour les systèmes de type Unix ou Linux, ce mécanisme de chargement en RAM uniquement des pages effectivement accédées est identique qu'il s'agisse de bibliothèques dynamiques ou statiques.
Il y a deux différences notables qui font que la compilation statique est une technique le plus souvent à oublier:
- si plusieurs programmes utilisent les mêmes bibliothèques, leur code ne sera chargé qu'une seule fois en RAM si dynamique mais autant de fois qu'il y a de programmes dans le cas de bibliothèques statiques d'ou gaspillage de mémoire et baisse des performances.
- un programme lié statiquement ne bénéficiera pas de corrections et améliorations apportées par les versions futures des bibliothèques qu'il utilise.
C'est la raison pour laquelle par exemple Solaris ne fournit plus de versions statique des bibliothèques système depuis 2005.
Pour moi, l’intérêt de compiler en statique, c'est de ne pas avoir à installer de bibliothèques, au détriment effectivement de la possibilité de mise à jour, et que 2 logiciels statiques utilisant la même bibliothèque utiliseront plus de mémoire du coup.
C'est un choix à faire.
Théoriquement, la compilation statique supprime les blocs inutilisés, tandis que la bibliothèque dynamique est indépendante de toute application.
La notion même de bibliothèque n'existe plus une fois liée statiquement au code de l'exécutable.
Les fonctions qu'elle définit sont dans le binaire, exactement comme les autres.
Bonjour,
Les bibliothèques statiques ne sont que des archives de fichiers objets et rien de plus. Pour créer une bibliothèque statique tu n'as d'ailleurs besoin que de tes fichiers objets et d'un archiveur. Sous linux par exemple l'archiveur utilisé est ar (il n'est à ma connaissance plus utilisé sauf pour créer des bibliothèques statiques). Il crée une archive à partir des fichiers objets et ajoute un index pour accéder rapidement au contenu (il y a longtemps un autre utilitaire ranlib construisait cet index, mais il est maintenant intégré à ar).
L'important dans la création d'une bibliothèque statique n'est pas tant sa création que la segmentation des fichiers objets. Imaginons que tu veuilles créer une bibliothèque statique libstat qui va proposer 3 fonctions : fct1, fct2, fc3. Supposons que fct1 se suffise à elle-même, et que fct2 appelle fct3.
Si tu définis les 3 fonctions dans un source bigsource.cqui donnera un objet bigsource.o alors lors de l'édition des liens il suffit que tu utilises une seule des trois des fonctions pour te retrouver avec le code des trois dans ton exécutable (bien que gcc par exemple permette aussi de faire de l'optimisation lors de l'édition des liens mais c'est plus difficile/délicat).
Si tu définis un source par fonction fct1.c, ... alors tu auras trois fichiers objets fct1.o, ... et lors de l'édition des liens seul le code des fonctions utilisées sera lié à ton exécutable, bien que cela dépende aussi des options de compilations. Si tu n'utilises que fct3 alors seul le code de fct3 sera lié, en revanche si tu utilises fct2 alors le code de fct2 et de fct3 seront liés.
Tout se passe exactement comme si tu n'utilisais pas de bibliothèque statique mais les objets qu'elle contient à la place.
Les détails précis dépendent de la chaîne de compilation utilisée.
Les bibliothèques dynamiques doivent contenir tout le code de tous les objets car on ne sait pas comment elles pourront être utilisées.
On peut lier dynamiquement, c'est-à-dire décider au moment de l''édition des liens que certaines parties de codes se trouveront ailleurs dans une bibliothèque dynamique, le loader de l'OS se débrouillera pour trouver tout ce qu'il faut.
On peut également charger à l'exécution une bibliothèque dynamique et y chercher du code qui sera exécuté par la suite (ce que font les plugins) mais là tout est à la charge de l'exécutable. Si je ne me trompe pas, toute la bibliothèque sera intégralement mappée en mémoire virtuelle dans les deux cas.
Les détails précis dépendent principalement de l'os.
Je suis étonné que le linker ne soit pas capable d'embarqué que fct1 si seule cette fonction est utilisée. L'édition des liens lie des fichiers .o entiers mais n'est pas capable d'en extraire que la partie utile ? :?
Je me demande si de possibles sauts dans le code ne seraient pas la cause de cela : on ne peut pas toujours prédire où l'on va sauter, donc on inclut l'intégralité du code de la lib...
Mais ça n'est qu'une supposition... :P
Prenons un exemple simple, un tout petit projet :
fichier bigsource.h :
fichier bigsource.c :Code:
1
2
3
4
5
6
7
8 #ifndef bigsource_h__ #define bigsource_h__ void fct1(void); void fct2(void); void fct3(void); #endif /* bigsource_h__ */
fichier fct4.h :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #include <stdio.h> #include "bigsource.h" void fct1(void) { printf("fct1\n"); } void fct2(void) { printf("fct2\n\t"); fct3(); } void fct3(void) { printf("fct3\n"); }
fichier fct4.c :Code:
1
2
3
4
5
6 #ifndef fct4_h__ #define fct4_h__ void fct4(void); #endif /* fct4_h__ */
fichier main.c :Code:
1
2
3
4
5
6
7
8 #include <stdio.h> #include "fct4.h" void fct4(void) { printf("fct4\n"); }
Et pour finir le Makefile :Code:
1
2
3
4
5
6
7
8 void fct3(void); int main(void) { fct3(); return 0; }
Ouf 8O !Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 CC = gcc CFLAGS = -ansi -Wall -Werror -O3 LDFLAGS = -O3 .PHONY: all clean all: test test-static test: main.o bigsource.o fct4.o $(CC) -o $@ $(LDFLAGS) $^ test-static: main.o libfct.a $(CC) -o $@ $(LDFLAGS) main.o -L. -lfct main.o: main.c bigsource.h fct4.h bigsource.o: bigsource.c bigsource.h fct4.o: fct4.c fct4.h libfct.a: bigsource.o fct4.o bigsource.h fct4.h ar -cvq $@ bigsource.o fct4.o clean: rm -rf *.o *.a test test-static *~
Commençons par un make. Cela nous crée deux exécutables test et test-static. Si nous explorons le code généré nous trouvons dans test :
En revanche dans test-static il n'y a aucun code généré pour fct4, mais il y en a pour fct1 et fct2. Pour examiner le code j'ai utilisé un objdump -ds.Code:
1
2
3
4
5
6
7
8
9
10
11
12 00000000004005b0 <fct1>: 4005b0: bf a4 06 40 00 mov $0x4006a4,%edi ... 00000000004005c0 <fct2>: 4005c0: 48 83 ec 08 sub $0x8,%rsp ... 00000000004005e0 <fct3>: 4005e0: bf b0 06 40 00 mov $0x4006b0,%edi ... 00000000004005f0 <fct4>: 4005f0: bf b5 06 40 00 mov $0x4006b5,%edi ...
J'ai tout placé dans Pièce jointe 120600.
Je me suis un peu trompé ... il semble qu'en passant par une bibliothèque statique le linker «optimise» mieux, à moins que ce soit gcc qui fasse exactement ce qu'on lui dit de faire (i.e. créer un exécutable avec les fichiers objets sans se poser plus de questions). À creuser ...
Je réouvre le poste suite aux compléments données.
Si je pars sur l'hypothèse que j'ai 4 fonctions dans une lib statique.
fct1 et fct2 dans un objet, fct3et 4 dans un autre ( en mettant les fonctions dans 2 fichiers .c diférents ). Les 2 objets de 2 fonctions dans la lib qui contient donc les 4 fonctions.
Si j'utilise uniquement la fct1, je présume donc que le compilateur va intégrer l'objet 1 contenant fct1 et 2 mais pas fct3 et 4, il aura donc incorporé fct2 pour rien mais l'aura fait car il intègre le fichier objet complet pas un bout de celui-ci.
En gros c'est ça. Mais comme je le précisais dans mon premier les détails vont dépendre de la chaîne de compilation utilisée.
En consultant le doc de gcc on y apprend que les optimisations durant la phase d'éditions des liens concernant les dépendances inter programme (-> LTO pour link-time optimization) n'est pas active par défaut. Pour l'activer il faut utiliser l'option -flto, il faut une version récente de gcc (au moins >=4 je suppose) et de ld (>=2.21).
En rajoutant dans le makefile cette option à la compilation et à l'édition des liens :
le code généré ne contient aucun code mort (seul du code pour fct3 est utilisé). En fait l'optimisation est encore plus agressive (ok ... j'ai un -O3 ...), aucun code n'est généré pour fct3, cette fonction est directement inlinée, un objdump -ds donne :Code:
1
2 CFLAGS = -ansi -Wall -Werror -O3 -flto LDFLAGS = -O3 -flto
Code:
1
2
3
4
5 0000000000400520 <main>: 400520: 48 83 ec 08 sub $0x8,%rsp 400524: bf d4 06 40 00 mov $0x4006d4,%edi 400529: e8 c2 ff ff ff callq 4004f0 <puts@plt> ...
Donc ça va très loin, je pense qu'il incorpore l'objet et donc les fonctions inutilisées et que la phase d'optimisation linker vire le code inutilisée. Ca va très loin.
Si je change de compilateur, il faut plutôt que je m'attende à ce qu'il incorpore les bibliothèques en entier. Vu les options de Gcc, il doit avoir un bouquin juste sur celles-ci.
Merci pour ces réponses plus utilises à satisfaire ma curiosité qu'autre chose.
:plusser: Expérimentations intéressantes. J'essayerais de m'en souvenir ^^
Je parlais ce matin avec un collègue d'aliasing de pointeurs, des optimisations qui vont avec, et d'optimisations en optimisations, je lui ai parlé du "problème" décrit, à savoir que l'intégralité des .o étaient embarqués. Il m'a répondu que c'était normal car un .o contient une seule section de codes avec toutes les fonctions. A l'édition des liens, le linker n'est pas capable de découper une section en morceaux et donc il embarque toutes les fonctions. Il m'a parlé d'options de gcc pour forcer le compilateur à créer plusieurs sections dans le .o pour permettre au linker de ne prendre que ce qui est nécessaire.
Les options sont les suivantes (http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html) :
Je n'ai pas testé le résultat sur ton code kwariz, mais il y a moyen de creuser de ce côté là ;)Citation:
-ffunction-sections
-fdata-sections
Place each function or data item into its own section in the output file if the target supports arbitrary sections. The name of the function or the name of the data item determines the section's name in the output file.
Use these options on systems where the linker can perform optimizations to improve locality of reference in the instruction space. Most systems using the ELF object format and SPARC processors running Solaris 2 have linkers with such optimizations. AIX may have these optimizations in the future.
Only use these options when there are significant benefits from doing so. When you specify these options, the assembler and linker create larger object and executable files and are also slower. You cannot use gprof on all systems if you specify this option, and you may have problems with debugging if you specify both this option and -g.
J'ai testé avec les options -ffunction-sections et -fdata-sections et les résultats sont identiques, au détail près que chaque fonction est dans sa propre section dans le fichier objet bigsource.o :
Mais dans l'exécutable il n'y a plus qu'une section .text.Code:
1
2
3
4
5
6
7
8 Disassembly of section .text.fct1: 0000000000000000 <fct1>: ... Disassembly of section .text.fct2: 0000000000000000 <fct2>: ... Disassembly of section .text.fct3: ...
Cela semble compatible avec le fait que même fct4 est inclus dans l'exécutable final bien qu'étant seul dans sa section et toute la section étant inutilisée.
Les résultats sont identiques en utilisant clang (sauf pour la partie lto que j'ai du mal à faire fonctionner), et en utilisant le linker gold plutôt que ld ...
Faut que je lise un peu plus d'internal de gcc ...
A l'inverse, dans certains cas, la compilation dynamique est a oublier :
- Probleme de mise a jour des versions : on ne compte plus les bibilotheques qui ne sont pas backward compatibles, et qui s'en foutent. Dans ce cas, un simple update de la bibliotheque, et ton programme ne fonctionne plus.
- Probleme des libs en matiere de securite : n'importe qui (ou presque) peut remplacer la lib dynamique par une version differente, changeant le comportement. C'est d'ailleurs pour cette raison qu'il n'y a pas (ou qu'il ne devrait pas y avoir) de lib incluant des fonctions de securite (login, cryptographie, ...)
ah? et libcrypto, libssl et libssh, c'est quoi?
C'est plus un problème de sécurité des libs et de l'environnement en général...
Mais ça n'est pas négligeable non plus comme argument en effet ! (imaginons un script online qui écrit dans les fichiers temporaire un .so/.dll, et force le navigateur à faire un export/setenv... tout peut arriver après !)
Normalement sur un système bien configuré, il faut déjà être admin pour ça.