51 votes

La liaison C++ est-elle suffisamment intelligente pour éviter la liaison de librairies inutilisées ?

Je suis loin de bien comprendre le fonctionnement de l'éditeur de liens C++ et j'ai une question spécifique à ce sujet.

Disons que j'ai les éléments suivants :

Utils.h

namespace Utils
{
    void func1();
    void func2();
}

Utils.cpp

#include "some_huge_lib" // needed only by func2()

namespace Utils
{
    void func1() { /* do something */ }
    void func2() { /* make use of some functions defined in some_huge_lib */ }
}

main.cpp

int main()
{
  Utils::func1()
}

Mon objectif est de générer des fichiers binaires aussi petits que possible.

Ma question est la suivante : est-ce que some_huge_lib être inclus dans le fichier objet de sortie ?

45voto

Marco A. Points 18535

L'inclusion ou l'édition de liens avec de grandes bibliothèques ne fait généralement pas de différence, sauf si vous utiliser ce genre de choses. Linkers devrait effectuer l'élimination du code mort et ainsi s'assurer qu'au moment de la construction, vous n'obtiendrez pas de gros binaires avec beaucoup de code inutilisé (lisez le manuel de votre compilateur/lien pour en savoir plus, ceci n'est pas imposé par la norme C++).

Inclure beaucoup d'en-têtes n'augmentera pas non plus la taille de votre binaire (mais cela pourrait augmenter substantiellement votre temps de compilation, cfr. les en-têtes précompilés). Il y a quelques exceptions pour les objets globaux et les bibliothèques dynamiques (qui ne peuvent pas être dépouillés). Je recommande également de lire ce passage ( gcc seulement ) concernant la séparation du code en plusieurs sections.

Une dernière remarque concernant les performances : si vous utilisez un fichier lot de code dépendant de la position (c'est-à-dire un code qui ne peut pas simplement être mappé à n'importe quelle adresse avec des décalages relatifs, mais qui a besoin d'un "rattrapage à chaud" via une table de relocalisation ou similaire), il y aura un coût de démarrage.

23voto

Tom Tanner Points 4148

Cela dépend d'un lot sur les outils et les commutateurs que vous utilisez pour lier et compiler.

Tout d'abord, si le lien some_huge_lib en tant que bibliothèque partagée, tout le code et les dépendances devront être résolus lors de la liaison de la bibliothèque partagée. Donc oui, il sera intégré quelque part.

Si vous faites un lien some_huge_lib comme une archive, alors - cela dépend. Pour la santé du lecteur, il est recommandé de placer func1 et func2 dans des fichiers de code source séparés, auquel cas l'éditeur de liens pourra généralement ignorer les fichiers objets inutilisés et leurs dépendances.

Si toutefois vous avez les deux fonctions dans le même fichier, vous devrez, sur certains compilateurs, leur demander de produire des sections individuelles pour chaque fonction. Certains compilateurs le font automatiquement, d'autres ne le font pas du tout. Si vous n'avez pas cette option, l'insertion de func1 entraînera l'insertion de tout le code de func2, et toutes les dépendances devront être résolues.

8voto

Adi Shavit Points 4470

Pensez à chaque fonction comme à un nœud dans un graphe.
Chaque nœud est associé à un morceau de code binaire - le binaire compilé de la fonction du nœud.
Il existe un lien (bord dirigé) entre 2 nœuds si un nœud (fonction) dépend (appelle) un autre.

Une bibliothèque statique est principalement une liste de tels nœuds (+ un index).

Le programme noeud de départ est le main() fonction.
Le site linker parcourt le graphe de main() y liens dans l'exécutable tous les noeuds qui sont atteignables à partir de main() . C'est pourquoi on l'appelle un linker (la liaison met en correspondance les adresses des appels de fonction dans l'exécutable).

Les fonctions inutilisées, n'ont pas de liens avec les nœuds du graphe émanant de main() .
Ainsi, ces nœuds déconnectés ne sont pas joignables et ne sont pas inclus dans l'exécutable final.

L'exécutable (par opposition à la bibliothèque statique) est principalement une liste de tous les nœuds atteignables à partir de main() (+ un index et un code de démarrage entre autres).

6voto

En plus des autres réponses, il faut dire que normalement les linkers travaillent en termes de sections et non de fonctions.

Les compilateurs ont généralement la possibilité de configurer s'ils mettent tout votre code objet dans une section monolithique ou s'ils le divisent en plusieurs sections plus petites. Par exemple, les options de GCC pour activer le fractionnement sont les suivantes -ffunction-sections (pour le code) et -fdata-sections (pour les données) ; l'option MSVC est /Gy (pour les deux). -fnofunction-sections , -fnodata-sections , /Gy- respectivement pour mettre tout le code ou les données dans une seule section.

Vous pouvez "jouer" avec la compilation de vos modules dans les deux modes et ensuite les vider ( objdump pour GCC, dumpbin pour MSVC) pour voir la structure du fichier objet généré.

Une fois qu'une section est formée par le compilateur, pour l'éditeur de liens, elle constitue une unité. Les sections définissent des symboles et font référence à des symboles définis dans d'autres sections. L'éditeur de liens construira un graphe de dépendance entre les sections (en commençant par un certain nombre de racines), puis il dissoudra ou conservera chacune d'entre elles entièrement. Ainsi, si vous avez une fonction utilisée et une fonction non utilisée dans une section, la fonction non utilisée sera conservée.

Les deux modes présentent des avantages et des inconvénients. Activer le fractionnement signifie des fichiers exécutables plus petits, mais des fichiers objets plus gros et des temps de liaison plus longs.

Il faut également noter qu'en C++, contrairement au C, il existe certaines situations où la règle de la définition unique est relâchée et où plusieurs définitions d'une fonction ou d'un objet de données sont autorisées (par exemple, dans le cas des fonctions en ligne). Les règles sont formulées de telle manière que l'éditeur de liens est autorisé à choisir n'importe quelle définition.

Du point de vue des sections, mettre les fonctions inline avec les fonctions non-inline signifierait que dans un scénario d'utilisation typique, l'éditeur de liens serait forcé de garder virtuellement chaque définition de chaque fonction inline ; cela signifierait un gonflement excessif du code. Par conséquent, ces fonctions et données sont normalement placées dans leurs propres sections, indépendamment des options de la ligne de commande du compilateur.

UPDATE : Comme @janm l'a correctement rappelé dans son commentaire, il faut également demander au linker de se débarrasser des sections non référencées en spécifiant --gc-sections (GNU) ou /opt:ref (MS).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X