Warum verursacht die Reihenfolge, in der Bibliotheken verknüpft sind, manchmal Fehler in GCC?


Antworten:


558

(Sehen Sie sich den Verlauf dieser Antwort an, um den ausführlicheren Text zu erhalten, aber ich denke jetzt, dass es für den Leser einfacher ist, echte Befehlszeilen zu sehen.)


Gemeinsame Dateien, die von allen unten aufgeführten Befehlen gemeinsam genutzt werden

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

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

$ cat d.cpp
int b;

Verknüpfung mit statischen Bibliotheken

$ 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

Der Linker sucht von links nach rechts und notiert dabei ungelöste Symbole. Wenn eine Bibliothek das Symbol auflöst, werden die Objektdateien dieser Bibliothek benötigt, um das Symbol aufzulösen (in diesem Fall bo aus libb.a).

Die Abhängigkeiten statischer Bibliotheken untereinander funktionieren gleich - die Bibliothek, die Symbole benötigt, muss zuerst die Bibliothek sein, die das Symbol auflöst.

Wenn eine statische Bibliothek von einer anderen Bibliothek abhängt, die andere Bibliothek jedoch wiederum von der vorherigen Bibliothek abhängt, gibt es einen Zyklus. Sie können dies beheben, indem Sie die zyklisch abhängigen Bibliotheken durch -(und einschließen -)(z. B. -( -la -lb -)(Sie müssen möglicherweise den Parens entkommen, z. B. -\(und -\)). Der Linker durchsucht dann die eingeschlossene Bibliothek mehrmals, um sicherzustellen, dass die Fahrradabhängigkeiten aufgelöst werden. Alternativ können Sie die Bibliotheken mehrmals angeben, sodass sie jeweils voreinander liegen : -la -lb -la.

Verknüpfung mit dynamischen Bibliotheken

$ 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

Hier ist es genauso - die Bibliotheken müssen den Objektdateien des Programms folgen. Der Unterschied zu statischen Bibliotheken besteht darin, dass Sie sich nicht um die Abhängigkeiten der Bibliotheken voneinander kümmern müssen, da dynamische Bibliotheken ihre Abhängigkeiten selbst sortieren .

Einige neuere Distributionen verwenden anscheinend standardmäßig das --as-neededLinker-Flag, wodurch erzwungen wird, dass die Objektdateien des Programms vor den dynamischen Bibliotheken stehen. Wenn dieses Flag übergeben wird, verknüpft der Linker keine Bibliotheken, die von der ausführbaren Datei nicht benötigt werden (und erkennt dies von links nach rechts). Meine aktuelle Archlinux-Distribution verwendet dieses Flag standardmäßig nicht, daher wurde kein Fehler ausgegeben, weil die richtige Reihenfolge nicht eingehalten wurde.

Es ist nicht richtig, die Abhängigkeit von b.sogegen d.sobeim Erstellen des ersteren wegzulassen . Sie müssen die Bibliothek dann beim Verknüpfen angeben a, abenötigen jedoch nicht wirklich die Ganzzahl bselbst, sodass die beigenen Abhängigkeiten nicht berücksichtigt werden sollten .

Hier ist ein Beispiel für die Auswirkungen, wenn Sie die Abhängigkeiten für nicht angeben 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"

Wenn Sie nun untersuchen, welche Abhängigkeiten die Binärdatei hat, stellen Sie fest, dass die Binärdatei selbst auch davon abhängt libd, nicht nur libbwie sie sollte. Die Binärdatei muss erneut verknüpft werden, wenn sie libbspäter von einer anderen Bibliothek abhängt. Wenn Sie dies auf diese Weise tun. Und wenn jemand andere Lasten libbmit dlopenzur Laufzeit (man denken an dem Laden von Plugins dynamisch), wird der Anruf als auch fehlschlagen. Also sollte das "right"wirklich auch so sein wrong.


10
Wiederholen Sie diesen Vorgang, bis alle Symbole aufgelöst sind. Sie würden denken, sie könnten eine topologische Sortierung verwalten. LLVM verfügt über 78 eigenständige statische Bibliotheken mit wer-weiß-was-Abhängigkeiten. Es stimmt, es gibt auch ein Skript, um Kompilierungs- / Linkoptionen herauszufinden - aber Sie können das nicht unter allen Umständen verwenden.
Steve314

6
@Steve das machen die programme lorder+ tsort. Aber manchmal gibt es keine Reihenfolge, wenn Sie zyklische Referenzen haben. Dann müssen Sie nur noch die Bibliotheksliste durchlaufen, bis alles gelöst ist.
Johannes Schaub - litb

10
@Johannes - Bestimmen Sie die maximal stark verbundenen Komponenten (z. B. Tarjans-Algorithmus) und sortieren Sie dann topologisch den (inhärent nicht zyklischen) Digraphen der Komponenten. Jede Komponente kann als eine Bibliothek behandelt werden. Wenn eine Bibliothek aus der Komponente benötigt wird, werden aufgrund der Abhängigkeitszyklen alle Bibliotheken in dieser Komponente benötigt. Nein, es ist wirklich nicht erforderlich, alle Bibliotheken zu durchlaufen, um alles aufzulösen, und es sind keine umständlichen Befehlszeilenoptionen erforderlich. Eine Methode mit zwei bekannten Algorithmen kann alle Fälle korrekt behandeln.
Steve314

4
Ich möchte dieser hervorragenden Antwort ein wichtiges Detail hinzufügen: Die Verwendung von "- (archives -)" oder "--start-group archives --end-group" ist seit jeher die einzige sichere Methode, um zirkuläre Abhängigkeiten aufzulösen Der Linker besucht ein Archiv und zieht (und registriert die nicht aufgelösten Symbole von) nur die Objektdateien, die derzeit nicht aufgelöste Symbole auflösen . Aus diesem Grund kann der CMake-Algorithmus zum Wiederholen verbundener Komponenten im Abhängigkeitsdiagramm gelegentlich fehlschlagen. (Siehe auch Ian Lance Taylors ausgezeichneter Blog-Beitrag über Linker für weitere Details.)
Jorgen

3
Ihre Antwort hat mir geholfen, meine Verknüpfungsfehler zu beheben, und Sie haben sehr klar erklärt, wie Sie Probleme vermeiden können. Haben Sie jedoch eine Idee, WARUM es so konzipiert wurde?
Anton Daneyko

102

Der GNU ld Linker ist ein sogenannter Smart Linker. Es verfolgt die Funktionen, die von vorhergehenden statischen Bibliotheken verwendet wurden, und wirft die Funktionen, die nicht verwendet werden, dauerhaft aus den Nachschlagetabellen. Wenn Sie eine statische Bibliothek zu früh verknüpfen, stehen die Funktionen in dieser Bibliothek statischen Bibliotheken später in der Verknüpfungszeile nicht mehr zur Verfügung.

Der typische UNIX-Linker funktioniert von links nach rechts. Platzieren Sie also alle abhängigen Bibliotheken links und diejenigen, die diese Abhängigkeiten erfüllen, rechts von der Linklinie. Möglicherweise stellen Sie fest, dass einige Bibliotheken von anderen abhängen, während andere Bibliotheken gleichzeitig von ihnen abhängen. Hier wird es kompliziert. Wenn es um Zirkelverweise geht, korrigieren Sie Ihren Code!


2
Ist das etwas mit nur gnu ld / gcc? Oder ist das bei Linkern üblich?
Mike

2
Anscheinend haben mehr Unix-Compiler ähnliche Probleme. MSVC ist nicht ganz frei von diesen Problemen, aber sie scheinen nicht so schlimm zu sein.
MSalters

4
Die MS-Entwicklungstools zeigen diese Probleme in der Regel nicht so häufig an, da bei Verwendung einer MS-Toolkette die Linkerreihenfolge ordnungsgemäß eingerichtet wird und Sie das Problem nie bemerken.
Michael Kohne

16
Der MSVC-Linker reagiert weniger empfindlich auf dieses Problem, da alle Bibliotheken nach einem nicht referenzierten Symbol durchsucht werden. Die Reihenfolge der Bibliotheken kann sich weiterhin darauf auswirken, welches Symbol aufgelöst wird, wenn mehr als eine Bibliothek das Symbol hat. Von MSDN: "Bibliotheken werden ebenfalls in Befehlszeilenreihenfolge durchsucht, mit der folgenden Einschränkung: Symbole, die beim Einbringen einer Objektdatei aus einer Bibliothek nicht aufgelöst werden, werden zuerst in dieser Bibliothek und dann in den folgenden Bibliotheken über die Befehlszeile und gesucht / DEFAULTLIB (Specify Default Library) Direktiven und dann zu allen Bibliotheken am Anfang der Befehlszeile "
Michael Burr

4
"... Smart Linker ..." - Ich glaube, es wird als "Single Pass" -Linker klassifiziert, nicht als "Smart Linker".
JWW

54

Hier ist ein Beispiel, um zu verdeutlichen, wie die Dinge mit GCC funktionieren, wenn statische Bibliotheken beteiligt sind. Nehmen wir also an, wir haben das folgende Szenario:

  • myprog.o- enthält main()Funktion, abhängig vonlibmysqlclient
  • libmysqlclient- statisch, um des Beispiels willen (Sie würden natürlich die gemeinsam genutzte Bibliothek bevorzugen, da diese libmysqlclientriesig ist); in /usr/local/lib; und abhängig von Sachen auslibz
  • libz (dynamisch)

Wie verknüpfen wir das? (Hinweis: Beispiele aus dem Kompilieren auf Cygwin mit 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

31

Wenn Sie -Wl,--start-groupden Linker-Flags hinzufügen , ist es egal, in welcher Reihenfolge sie sich befinden oder ob zirkuläre Abhängigkeiten bestehen.

Auf Qt bedeutet dies das Hinzufügen von:

QMAKE_LFLAGS += -Wl,--start-group

Spart viel Zeit beim Herumspielen und scheint die Verknüpfung nicht viel zu verlangsamen (was ohnehin viel weniger Zeit in Anspruch nimmt als das Kompilieren).


8

Eine andere Alternative wäre, die Liste der Bibliotheken zweimal anzugeben:

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

Auf diese Weise müssen Sie sich nicht um die richtige Reihenfolge kümmern, da die Referenz im zweiten Block aufgelöst wird.


5

Sie können die Option -Xlinker verwenden.

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

ist fast gleich

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

Vorsichtig !

  1. Die Reihenfolge innerhalb einer Gruppe ist wichtig! Hier ein Beispiel: Eine Debug-Bibliothek verfügt über eine Debug-Routine, die Nicht-Debug-Bibliothek verfügt jedoch über eine schwache Version derselben. Sie müssen die Debug-Bibliothek ZUERST in die Gruppe aufnehmen, sonst werden Sie in die Nicht-Debug-Version auflösen.
  2. Sie müssen jeder Bibliothek in der Gruppenliste -Xlinker voranstellen

5

Ein kurzer Tipp, der mich auslöste: Wenn Sie den Linker als "gcc" oder "g ++" aufrufen, werden diese Optionen bei Verwendung von "--start-group" und "--end-group" nicht an die weitergeleitet Linker - noch wird es einen Fehler kennzeichnen. Die Verknüpfung mit undefinierten Symbolen schlägt nur fehl, wenn die Bibliotheksreihenfolge falsch war.

Sie müssen sie als "-Wl, - start-group" usw. schreiben, um GCC anzuweisen, das Argument an den Linker weiterzuleiten.


2

Die Linkreihenfolge spielt sicherlich eine Rolle, zumindest auf einigen Plattformen. Ich habe Abstürze für Anwendungen gesehen, die mit Bibliotheken in falscher Reihenfolge verknüpft sind (wobei falsch bedeutet, dass A vor B verknüpft ist, B jedoch von A abhängt).


2

Ich habe dies oft gesehen, einige unserer Module verknüpfen mehr als 100 Bibliotheken unseres Codes plus System- und Drittanbieter-Bibliotheken.

Abhängig von verschiedenen Linkern HP / Intel / GCC / SUN / SGI / IBM / usw. können Sie ungelöste Funktionen / Variablen usw. erhalten. Auf einigen Plattformen müssen Sie Bibliotheken zweimal auflisten.

Zum größten Teil verwenden wir eine strukturierte Hierarchie von Bibliotheken, Kern, Plattform und verschiedenen Abstraktionsebenen, aber für einige Systeme müssen Sie immer noch mit der Reihenfolge im Befehl link spielen.

Sobald Sie auf ein Lösungsdokument gestoßen sind, muss der nächste Entwickler es nicht erneut ausarbeiten.

Mein alter Dozent sagte immer: " Hoher Zusammenhalt und niedrige Kopplung ", das ist bis heute so.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.