Lors de la création d'une bibliothèque de classes en C ++, vous pouvez choisir entre les bibliothèques dynamiques ( .dll
, .so
) et statiques ( .lib
, .a
). Quelle est la différence entre eux et quand est-il approprié de les utiliser?
Lors de la création d'une bibliothèque de classes en C ++, vous pouvez choisir entre les bibliothèques dynamiques ( .dll
, .so
) et statiques ( .lib
, .a
). Quelle est la différence entre eux et quand est-il approprié de les utiliser?
Réponses:
Les bibliothèques statiques augmentent la taille du code dans votre binaire. Ils sont toujours chargés et quelle que soit la version du code avec laquelle vous avez compilé, c'est la version du code qui s'exécutera.
Les bibliothèques dynamiques sont stockées et versionnées séparément. Il est possible de charger une version de la bibliothèque dynamique qui n'était pas celle d'origine livrée avec votre code si la mise à jour est considérée comme binaire compatible avec la version d'origine.
De plus, les bibliothèques dynamiques ne sont pas nécessairement chargées - elles sont généralement chargées lors du premier appel - et peuvent être partagées entre les composants qui utilisent la même bibliothèque (plusieurs chargements de données, un seul chargement de code).
Les bibliothèques dynamiques étaient considérées comme la meilleure approche la plupart du temps, mais à l'origine, elles présentaient un défaut majeur (enfer de DLL Google), qui a pratiquement été éliminé par les systèmes d'exploitation Windows plus récents (Windows XP en particulier).
D'autres ont bien expliqué ce qu'est une bibliothèque statique, mais je voudrais souligner certaines des mises en garde de l'utilisation de bibliothèques statiques, au moins sous Windows:
Singletons: si quelque chose doit être global / statique et unique, faites très attention à le mettre dans une bibliothèque statique. Si plusieurs DLL sont liées à cette bibliothèque statique, elles obtiendront chacune leur propre copie du singleton. Toutefois, si votre application est un seul EXE sans DLL personnalisée, cela ne peut pas être un problème.
Suppression de code non référencé: lorsque vous établissez une liaison avec une bibliothèque statique, seules les parties de la bibliothèque statique référencées par votre DLL / EXE seront liées à votre DLL / EXE.
Par exemple, si mylib.lib
contient a.obj
et b.obj
et que votre DLL / EXE ne fait référence qu'à des fonctions ou des variables de a.obj
, l'intégralité de b.obj
sera supprimée par l'éditeur de liens. Si b.obj
contient des objets globaux / statiques, leurs constructeurs et destructeurs ne seront pas exécutés. Si ces constructeurs / destructeurs ont des effets secondaires, vous pouvez être déçu de leur absence.
De même, si la bibliothèque statique contient des points d'entrée spéciaux, vous devrez peut-être veiller à ce qu'ils soient réellement inclus. Un exemple de cela dans la programmation intégrée (d'accord, pas Windows) serait un gestionnaire d'interruption qui est marqué comme étant à une adresse spécifique. Vous devez également marquer le gestionnaire d'interruption comme un point d'entrée pour vous assurer qu'il ne sera pas supprimé.
Une autre conséquence de cela est qu'une bibliothèque statique peut contenir des fichiers objets qui sont complètement inutilisables en raison de références non résolues, mais cela ne provoquera pas d'erreur de l'éditeur de liens tant que vous ne référencerez pas une fonction ou une variable à partir de ces fichiers objets. Cela peut se produire longtemps après l'écriture de la bibliothèque.
Symboles de débogage: vous souhaiterez peut-être une PDB distincte pour chaque bibliothèque statique, ou vous souhaiterez peut-être que les symboles de débogage soient placés dans les fichiers objet afin qu'ils soient roulés dans la PDB pour la DLL / EXE. La documentation de Visual C ++ explique les options nécessaires .
RTTI: vous pouvez vous retrouver avec plusieurs type_info
objets pour la même classe si vous liez une seule bibliothèque statique à plusieurs DLL. Si votre programme suppose que ce type_info
sont des données "singleton" et utilise &typeid()
ou type_info::before()
, vous pouvez obtenir des résultats indésirables et surprenants.
Une lib est une unité de code intégrée à l'exécutable de votre application.
Une dll est une unité autonome de code exécutable. Il n'est chargé dans le processus que lorsqu'un appel est effectué dans ce code. Une dll peut être utilisée par plusieurs applications et chargée dans plusieurs processus, tout en n'ayant qu'une seule copie du code sur le disque dur.
Dll pros : peut être utilisé pour réutiliser / partager du code entre plusieurs produits; charger dans la mémoire de processus sur demande et peut être déchargé lorsqu'il n'est pas nécessaire; peut être mis à niveau indépendamment du reste du programme.
Contre la DLL: impact sur les performances du chargement de la DLL et du rebasage du code; problèmes de version ("dll hell")
Avantages de Lib : aucun impact sur les performances car le code est toujours chargé dans le processus et n'est pas rebasé; aucun problème de version.
Lib cons : exécutable / processus "bloat" - tout le code est dans votre exécutable et est chargé au démarrage du processus; pas de réutilisation / partage - chaque produit a sa propre copie du code.
Outre les implications techniques des bibliothèques statiques vs dynamiques (les fichiers statiques regroupent tout dans une grande bibliothèque binaire vs dynamique qui permet le partage de code entre plusieurs exécutables différents), il y a les implications juridiques .
Par exemple, si vous utilisez du code sous licence LGPL et que vous vous liez statiquement à une bibliothèque LGPL (et créez ainsi un gros binaire), votre code devient automatiquement Open Source ( gratuit comme en toute liberté) code LGPL. Si vous vous liez à des objets partagés, vous n'avez besoin que de LGPL les améliorations / corrections de bogues que vous apportez à la bibliothèque LGPL elle-même.
Cela devient un problème beaucoup plus important si vous décidez comment compiler vos applications mobiles par exemple (dans Android, vous avez le choix entre statique et dynamique, dans iOS vous ne l'avez pas - c'est toujours statique).
Les programmes C ++ sont construits en deux phases
La bibliothèque statique (.lib) n'est qu'un ensemble de fichiers .obj et n'est donc pas un programme complet. Il n'a pas subi la deuxième phase (de liaison) de construction d'un programme. Les DLL, d'autre part, sont comme des exes et sont donc des programmes complets.
Si vous construisez une bibliothèque statique, elle n'est pas encore liée et donc les consommateurs de votre bibliothèque statique devront utiliser le même compilateur que vous avez utilisé (si vous avez utilisé g ++, ils devront utiliser g ++).
Si au lieu de cela vous avez construit une DLL (et que vous l'avez construite correctement ), vous avez construit un programme complet que tous les consommateurs peuvent utiliser, quel que soit le compilateur qu'ils utilisent. Il existe cependant plusieurs restrictions, lors de l'exportation à partir d'une DLL, si la compatibilité entre compilateurs croisés est souhaitée.
consumers of your static library will have to use the same compiler that you used
si la bibliothèque statique utilise une bibliothèque C ++, telle que #include <iostream>
.
$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
cc -o hello hello.o -L. -ltest
hello.o: hello.c
cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
ar cr libtest.a foo.o foo2.o
foo.o:foo.c
cc -c foo.c
foo2.o:foo.c
cc -c foo2.c
clean:
rm -f foo.o foo2.o libtest.a hello.o
$$:~/static [38]>
$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
cc -o hello hello.o -L`pwd` -ltest
hello.o:
cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
cc -c -b foo.c
foo2.o:foo.c
cc -c -b foo2.c
clean:
rm -f libtest.sl foo.o foo
2.o hello.o
$$:~/dynamic [50]>
Une bibliothèque statique est compilée dans le client. Un .lib est utilisé au moment de la compilation et le contenu de la bibliothèque fait partie de l'exécutable consommateur.
Une bibliothèque dynamique est chargée au moment de l'exécution et n'est pas compilée dans l'exécutable client. Les bibliothèques dynamiques sont plus flexibles car plusieurs exécutables clients peuvent charger une DLL et utiliser ses fonctionnalités. Cela réduit également la taille globale et la maintenabilité de votre code client au minimum.
Vous devez réfléchir attentivement aux changements au fil du temps, au versioning, à la stabilité, à la compatibilité, etc.
S'il y a deux applications qui utilisent le code partagé, voulez-vous forcer ces applications à changer ensemble, au cas où elles devraient être compatibles entre elles? Utilisez ensuite la DLL. Tous les exes utiliseront le même code.
Ou voulez-vous les isoler les uns des autres, afin de pouvoir en changer un et être sûr de ne pas avoir cassé l'autre. Utilisez ensuite la bibliothèque statique.
L'enfer de DLL, c'est quand vous auriez probablement dû utiliser une bibliothèque statique, mais vous avez utilisé une DLL à la place, et tous les exes ne sont pas compatibles avec elle.
Une bibliothèque statique doit être liée à l'exécutable final; il fait partie de l'exécutable et le suit partout où il va. Une bibliothèque dynamique est chargée à chaque exécution de l'exécutable et reste distincte de l'exécutable sous forme de fichier DLL.
Vous utiliseriez une DLL lorsque vous souhaitez pouvoir modifier les fonctionnalités fournies par la bibliothèque sans avoir à lier à nouveau l'exécutable (il suffit de remplacer le fichier DLL, sans avoir à remplacer le fichier exécutable).
Vous utiliseriez une bibliothèque statique chaque fois que vous n'avez aucune raison d'utiliser une bibliothèque dynamique.
L'article d'Ulrich Drepper sur " Comment écrire des bibliothèques partagées " est également une bonne ressource qui détaille la meilleure façon de tirer parti des bibliothèques partagées, ou ce qu'il appelle les "objets partagés dynamiques" (DSO). Il se concentre davantage sur les bibliothèques partagées au format binaire ELF , mais certaines discussions conviennent également aux DLL Windows.
Pour une excellente discussion sur ce sujet, lisez cet article de Sun.
Il rentre dans tous les avantages, y compris la possibilité d'insérer des bibliothèques d'interposition. Plus de détails sur l'interposition peuvent être trouvés dans cet article ici .
Vraiment, le compromis que vous faites (dans un grand projet) est dans le temps de chargement initial, les bibliothèques vont être liées à un moment ou à un autre, la décision qui doit être prise est que le lien prendra assez de temps pour que le compilateur ait besoin pour mordre la balle et le faire à l'avant, ou l'éditeur de liens dynamique peut-il le faire au moment du chargement.
Si votre bibliothèque va être partagée entre plusieurs exécutables, il est souvent judicieux de la rendre dynamique pour réduire la taille des exécutables. Sinon, rendez-le définitivement statique.
Il existe plusieurs inconvénients à utiliser une DLL. Il y a des frais supplémentaires pour le charger et le décharger. Il existe également une dépendance supplémentaire. Si vous modifiez la DLL pour la rendre incompatible avec vos exécutables, ils cesseront de fonctionner. D'un autre côté, si vous changez une bibliothèque statique, vos exécutables compilés utilisant l'ancienne version ne seront pas affectés.
Si la bibliothèque est statique, au moment de la liaison, le code est lié à votre exécutable. Cela rend votre exécutable plus grand (que si vous aviez choisi la route dynamique).
Si la bibliothèque est dynamique, au moment de la liaison, des références aux méthodes requises sont intégrées à votre exécutable. Cela signifie que vous devez expédier votre exécutable et la bibliothèque dynamique. Vous devez également déterminer si l'accès partagé au code dans la bibliothèque est une adresse de chargement sûre et préférée, entre autres.
Si vous pouvez vivre avec la bibliothèque statique, optez pour la bibliothèque statique.
Nous utilisons beaucoup de DLL (> 100) dans notre projet. Ces DLL ont des dépendances les unes des autres et nous avons donc choisi la configuration de la liaison dynamique. Cependant, il présente les inconvénients suivants:
Peut-être qu'une meilleure configuration était de faire de tout une bibliothèque statique (et donc vous n'avez qu'un exécutable). Cela ne fonctionne que si aucune duplication de code n'a lieu. Un test semble soutenir cette hypothèse, mais je n'ai pas trouvé de devis MSDN officiel. Donc, par exemple, faites 1 exe avec:
Le code et les variables de shared_lib2 ne devraient être présents qu'une seule fois dans l'exécutable fusionné final. Quelqu'un peut-il soutenir cette question?
Les bibliothèques statiques sont des archives qui contiennent le code objet de la bibliothèque, lorsqu'elles sont liées à une application, ce code est compilé dans l'exécutable. Les bibliothèques partagées sont différentes en ce qu'elles ne sont pas compilées dans l'exécutable. Au lieu de cela, l'éditeur de liens dynamique recherche certains répertoires à la recherche des bibliothèques dont il a besoin, puis les charge en mémoire. Plus d'un exécutable peut utiliser la même bibliothèque partagée en même temps, réduisant ainsi l'utilisation de la mémoire et la taille de l'exécutable. Cependant, il y a alors plus de fichiers à distribuer avec l'exécutable. Vous devez vous assurer que la bibliothèque est installée sur le système utilise quelque part où l'éditeur de liens peut le trouver, la liaison statique élimine ce problème mais entraîne un fichier exécutable plus volumineux.
Si votre travail sur des projets intégrés ou des bibliothèques statiques de plates-formes spécialisées est la seule façon de procéder, il est également souvent moins compliqué de les compiler dans votre application. Le fait d'avoir des projets et un makefile comprenant tout rend la vie plus heureuse.
Je donnerais une règle générale que si vous avez une grande base de code, toutes construites sur des bibliothèques de niveau inférieur (par exemple un framework Utils ou Gui), que vous souhaitez partitionner en bibliothèques plus gérables, puis en faire des bibliothèques statiques. Les bibliothèques dynamiques ne vous achètent vraiment rien et il y a moins de surprises - il n'y aura qu'une seule instance de singletons par exemple.
Si vous avez une bibliothèque qui est entièrement séparée du reste de la base de code (par exemple une bibliothèque tierce), envisagez d'en faire une DLL. Si la bibliothèque est LGPL, vous devrez peut-être utiliser une DLL de toute façon en raison des conditions de licence.