Pourquoi l'ordre dans lequel les bibliothèques sont liées provoque-t-il parfois des erreurs dans GCC?

449

Pourquoi l'ordre dans lequel les bibliothèques sont liées provoque-t-il parfois des erreurs dans GCC?

Se poser sur
la source
Voir aussi maintenant stackoverflow.com/questions/7826448/… - TLDR a gccchangé pour un comportement plus strict (relativement) récemment.
tripleee

Réponses:

558

(Voir l'historique de cette réponse pour obtenir le texte plus élaboré, mais je pense maintenant qu'il est plus facile pour le lecteur de voir de vraies lignes de commande).


Fichiers communs partagés par toutes les commandes ci-dessous

$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

Lien vers des bibliothèques statiques

$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o

$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order

L'éditeur de liens effectue une recherche de gauche à droite et note les symboles non résolus au fur et à mesure. Si une bibliothèque résout le symbole, il faut les fichiers objets de cette bibliothèque pour résoudre le symbole (bo dans libb.a dans ce cas).

Les dépendances des bibliothèques statiques les unes par rapport aux autres fonctionnent de la même manière - la bibliothèque qui a besoin de symboles doit être en premier, puis la bibliothèque qui résout le symbole.

Si une bibliothèque statique dépend d'une autre bibliothèque, mais que l'autre bibliothèque dépend à nouveau de l'ancienne bibliothèque, il y a un cycle. Vous pouvez résoudre ce problème en enfermant les bibliothèques cycliquement dépendantes par -(et -), comme -( -la -lb -)(vous devrez peut-être échapper aux parens, comme -\(et -\)). L'éditeur de liens recherche ensuite ces bibliothèques incluses plusieurs fois pour s'assurer que les dépendances cycliques sont résolues. Alternativement, vous pouvez spécifier les bibliothèques plusieurs fois, de sorte que chacune est l'une devant l'autre:-la -lb -la .

Lien vers des bibliothèques dynamiques

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!

$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order

C'est la même chose ici - les bibliothèques doivent suivre les fichiers objets du programme. La différence ici par rapport aux bibliothèques statiques est que vous n'avez pas à vous soucier des dépendances des bibliothèques les unes contre les autres, car les bibliothèques dynamiques trient elles-mêmes leurs dépendances .

Certaines distributions récentes semblent utiliser par défaut le --as-needed indicateur de éditeur de liens, qui impose que les fichiers objets du programme passent avant les bibliothèques dynamiques. Si cet indicateur est passé, l'éditeur de liens ne sera pas lié à des bibliothèques qui ne sont pas réellement nécessaires à l'exécutable (et il le détecte de gauche à droite). Ma récente distribution archlinux n'utilise pas cet indicateur par défaut, donc il n'a pas donné d'erreur pour ne pas suivre l'ordre correct.

Il n'est pas correct d'omettre la dépendance de b.socontre d.solors de la création de la première. Vous devrez alors spécifier la bibliothèque lors de la liaison a, mais avous n'avez pas vraiment besoin de l'entier blui-même, donc il ne devrait pas être fait pour se soucier bdes propres dépendances de.

Voici un exemple des implications si vous ne spécifiez pas les dépendances pour libb.so

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)

$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"

Si vous examinez maintenant les dépendances du binaire, vous noterez que le binaire lui-même dépend également libd, pas seulement libbcomme il se doit. Le binaire devra être relié si libbplus tard dépend d'une autre bibliothèque, si vous le faites de cette façon. Et si quelqu'un d'autre se charge d' libbutiliser dlopenau moment de l'exécution (pensez à charger dynamiquement les plugins), l'appel échouera également. Donc, le "right"devrait aussi être un wrong.

Johannes Schaub - litb
la source
10
Répétez jusqu'à ce que tous les symboles soient résolus, hein - vous penseriez qu'ils pourraient gérer un tri topologique. LLVM possède à lui seul 78 bibliothèques statiques, avec des dépendances qui sait quoi. Certes, il dispose également d'un script pour comprendre les options de compilation / lien - mais vous ne pouvez pas l'utiliser dans toutes les circonstances.
Steve314
6
@Steve, c'est ce que font les programmes lorder+ tsort. Mais parfois, il n'y a pas d'ordre, si vous avez des références cycliques. Ensuite, il vous suffit de parcourir la liste des bibliothèques jusqu'à ce que tout soit résolu.
Johannes Schaub - litb
10
@Johannes - Déterminez les composants maximaux fortement connectés (par exemple l'algorithme de Tarjans) puis triez topologiquement le digraphe (intrinsèquement non cyclique) des composants. Chaque composant peut être traité comme une bibliothèque - si une bibliothèque du composant est nécessaire, le ou les cycles de dépendance entraîneront la nécessité de toutes les bibliothèques de ce composant. Donc non, il n'y a vraiment pas besoin de parcourir toutes les bibliothèques pour tout résoudre, et pas besoin d'options de ligne de commande maladroites - une méthode utilisant deux algorithmes bien connus peut gérer tous les cas correctement.
Steve314
4
Je voudrais ajouter un détail important à cette excellente réponse: Utiliser "- (archives -)" ou "--start-group archives --end-group" est le seul moyen sûr de résoudre les dépendances circulaires , car à chaque fois l'éditeur de liens visite une archive, il extrait (et enregistre les symboles non résolus de) uniquement les fichiers objets qui résolvent les symboles actuellement non résolus . Pour cette raison, l'algorithme de CMake de répétition des composants connectés dans le graphique de dépendance peut parfois échouer. (Voir aussi l'excellent article de blog de Ian Lance Taylor sur les éditeurs de liens pour plus de détails.)
jorgen
3
Votre réponse m'a aidé à résoudre mes erreurs de liaison et vous avez très clairement expliqué COMMENT éviter d'avoir des ennuis, mais avez-vous une idée POURQUOI a-t-il été conçu pour fonctionner de cette façon?
Anton Daneyko
102

L'éditeur de liens GNU ld est un éditeur de liens intelligent. Il gardera une trace des fonctions utilisées par les bibliothèques statiques précédentes, jetant en permanence les fonctions qui ne sont pas utilisées à partir de ses tables de recherche. Le résultat est que si vous liez une bibliothèque statique trop tôt, les fonctions de cette bibliothèque ne sont plus disponibles pour les bibliothèques statiques plus tard sur la ligne de liaison.

L'éditeur de liens UNIX typique fonctionne de gauche à droite, alors placez toutes vos bibliothèques dépendantes à gauche et celles qui satisfont ces dépendances à droite de la ligne de liaison. Vous pouvez constater que certaines bibliothèques dépendent d'autres alors que d'autres bibliothèques en dépendent. C'est là que ça se complique. En ce qui concerne les références circulaires, corrigez votre code!

décodeur
la source
2
Est-ce quelque chose avec seulement gnu ld / gcc? Ou est-ce quelque chose de commun avec les linkers?
Mike
2
Apparemment, plus de compilateurs Unix ont des problèmes similaires. MSVC n'est pas entièrement exempt de ces problèmes, eiher, mais ils ne semblent pas si mauvais.
MSalters le
4
Les outils de développement MS n'ont pas tendance à afficher autant ces problèmes car si vous utilisez une chaîne d'outils entièrement MS, cela finit par configurer correctement l'ordre de l'éditeur de liens et vous ne remarquez jamais le problème.
Michael Kohne
16
L'éditeur de liens MSVC est moins sensible à ce problème car il recherchera dans toutes les bibliothèques un symbole non référencé. Pour la bibliothèque peut encore affecter ce qui symbole se résoudre si plus d'une bibliothèque ont le symbole. À partir de MSDN: "Les bibliothèques sont également recherchées dans l'ordre de ligne de commande, avec la mise en garde suivante: les symboles qui ne sont pas résolus lors de l'importation d'un fichier objet à partir d'une bibliothèque sont d'abord recherchés dans cette bibliothèque, puis les bibliothèques suivantes à partir de la ligne de commande et / DEFAULTLIB (spécifier la bibliothèque par défaut), puis à toutes les bibliothèques au début de la ligne de commande "
Michael Burr
4
"... éditeur de liens intelligent ..." - Je crois qu'il est classé comme un éditeur de liens "à un seul passage", pas un "éditeur de liens intelligent".
jww
54

Voici un exemple pour montrer clairement comment les choses fonctionnent avec GCC lorsque des bibliothèques statiques sont impliquées. Supposons donc que nous avons le scénario suivant:

  • myprog.o- contenant la main()fonction, dépend delibmysqlclient
  • libmysqlclient- statique, pour les besoins de l'exemple (vous préférez la bibliothèque partagée, bien sûr, car elle libmysqlclientest énorme); dans /usr/local/lib; et dépend de choses delibz
  • libz (dynamique)

Comment lions-nous cela? (Remarque: exemples de compilation sur Cygwin à l'aide de gcc 4.3.4)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works
Lumi
la source
31

Si vous ajoutez -Wl,--start-group aux indicateurs de l'éditeur de liens, peu importe l'ordre dans lequel ils se trouvent ou s'il existe des dépendances circulaires.

Sur Qt, cela signifie ajouter:

QMAKE_LFLAGS += -Wl,--start-group

Économise beaucoup de temps et ne semble pas ralentir beaucoup la liaison (ce qui prend beaucoup moins de temps que la compilation de toute façon).

SvaLopLop
la source
8

Une autre alternative serait de spécifier deux fois la liste des bibliothèques:

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

Ce faisant, vous n'avez pas à vous soucier de la bonne séquence, car la référence sera résolue dans le deuxième bloc.

eckes
la source
5

Vous pouvez utiliser l'option -Xlinker.

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

est PRESQUE égal à

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

Prudent !

  1. L'ordre au sein d'un groupe est important! Voici un exemple: une bibliothèque de débogage a une routine de débogage, mais la bibliothèque sans débogage a une version faible de la même. Vous devez placer la bibliothèque de débogage en PREMIER dans le groupe, sinon vous passerez à la version sans débogage.
  2. Vous devez faire précéder chaque bibliothèque de la liste des groupes avec -Xlinker
yundorri
la source
5

Un petit conseil qui m'a fait trébucher: si vous appelez l'éditeur de liens en tant que "gcc" ou "g ++", alors l'utilisation de "--start-group" et "--end-group" ne transmettra pas ces options au éditeur de liens - il ne signalera pas non plus d'erreur. Il échouera simplement le lien avec des symboles non définis si vous avez mal commandé la bibliothèque.

Vous devez les écrire comme "-Wl, - start-group" etc. pour dire à GCC de passer l'argument à l'éditeur de liens.

MM
la source
2

L'ordre des liens est certainement important, du moins sur certaines plateformes. J'ai vu des plantages pour des applications liées à des bibliothèques dans le mauvais ordre (où mauvais signifie A lié avant B mais B dépend de A).

David Cournapeau
la source
2

J'ai beaucoup vu cela, certains de nos modules lient plus de 100 bibliothèques de notre code plus le système et des bibliothèques tierces.

En fonction des différents éditeurs de liens HP / Intel / GCC / SUN / SGI / IBM / etc, vous pouvez obtenir des fonctions / variables non résolues, etc. Sur certaines plates-formes, vous devez répertorier les bibliothèques deux fois.

Pour la plupart, nous utilisons une hiérarchie structurée de bibliothèques, noyau, plate-forme, différentes couches d'abstraction, mais pour certains systèmes, vous devez toujours jouer avec l'ordre dans la commande de lien.

Une fois que vous avez trouvé une solution, documentez-la afin que le développeur suivant n'ait pas à la retravailler.

Mon ancien conférencier disait « haute cohésion et faible couplage », c'est toujours vrai aujourd'hui.

titanae
la source