Perché l'ordine in cui le librerie sono collegate a volte causa errori in GCC?


Risposte:


558

(Vedi la cronologia di questa risposta per ottenere il testo più elaborato, ma ora penso che sia più facile per il lettore vedere righe di comando reali).


File comuni condivisi da tutti i comandi seguenti

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

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

$ cat d.cpp
int b;

Collegamento a librerie statiche

$ 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

Il linker cerca da sinistra a destra e nota i simboli non risolti mentre procede. Se una libreria risolve il simbolo, ci vogliono i file oggetto di quella libreria per risolvere il simbolo (bo in libb.a in questo caso).

Le dipendenze delle librerie statiche l'una contro l'altra funzionano allo stesso modo: la libreria che necessita di simboli deve essere prima la libreria che risolve il simbolo.

Se una libreria statica dipende da un'altra libreria, ma l'altra libreria dipende nuovamente dalla libreria precedente, esiste un ciclo. È possibile risolvere questo problema racchiudendo le librerie ciclicamente dipendenti da -(e -), come -( -la -lb -)(potrebbe essere necessario sfuggire alle parentesi, come -\(e -\)). Il linker quindi cerca le librerie allegate più volte per garantire la risoluzione delle dipendenze cicliche. In alternativa, è possibile specificare le librerie più volte, in modo da ciascuno è prima di uno con l'altro: -la -lb -la.

Collegamento a librerie dinamiche

$ 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

È lo stesso qui: le librerie devono seguire i file oggetto del programma. La differenza qui rispetto alle librerie statiche è che non è necessario preoccuparsi delle dipendenze delle librerie l'una contro l'altra, poiché le librerie dinamiche risolvono autonomamente le loro dipendenze .

Apparentemente alcune distribuzioni recenti utilizzano per default il --as-neededflag linker, il che impone che i file oggetto del programma vengano prima delle librerie dinamiche. Se quel flag viene passato, il linker non si collegherà alle librerie che non sono effettivamente necessarie all'eseguibile (e lo rileva da sinistra a destra). La mia recente distribuzione archlinux non usa questo flag di default, quindi non ha dato un errore per non aver seguito l'ordine corretto.

Non è corretto omettere la dipendenza di b.socontro d.soquando si crea il primo. Ti verrà richiesto di specificare la libreria al momento del collegamento a, ma in arealtà non ha bisogno dell'intero bstesso, quindi non è necessario preoccuparsi bdelle proprie dipendenze.

Ecco un esempio delle implicazioni se ti manca specificare le dipendenze per 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"

Se ora guardi a quali dipendenze ha il binario, noti che anche il binario stesso dipende libd, non solo libbcome dovrebbe. Il binario dovrà essere ricollegato se in libbseguito dipende da un'altra libreria, se lo fai in questo modo. E se qualcun altro carica libbusando dlopenin fase di runtime (pensa a caricare i plugin in modo dinamico), anche la chiamata fallirà. Quindi "right"dovrebbe essere anche un wrong.


10
Ripeti fino a quando tutti i simboli non si saranno risolti, eh, potresti pensare che possano gestire un ordinamento topologico. LLVM ha 78 librerie statiche per conto proprio, con chissà quali dipendenze. È vero che ha anche uno script per capire le opzioni di compilazione / collegamento, ma non puoi usarlo in tutte le circostanze.
Steve314,

6
@Steve è quello che fanno i programmi lorder+ tsort. Ma a volte non c'è ordine, se hai riferimenti ciclici. Quindi devi solo scorrere l'elenco delle librerie fino a quando tutto non viene risolto.
Johannes Schaub - litb

10
@Johannes - Determina i componenti massimi fortemente connessi (ad esempio l'algoritmo di Tarjans), quindi ordina topologicamente il digrafo (intrinsecamente non ciclico) dei componenti. Ogni componente può essere trattato come una libreria: se è necessaria una libreria del componente, i cicli di dipendenza causeranno la necessità di tutte le librerie in quel componente. Quindi no, non c'è davvero bisogno di scorrere tutte le librerie per risolvere tutto, e non c'è bisogno di opzioni imbarazzanti da riga di comando - un metodo che usa due algoritmi ben noti può gestire correttamente tutti i casi.
Steve314,

4
Vorrei aggiungere un dettaglio importante a questa eccellente risposta: usare "- (archives -)" o "--start-group archives --end-group" è l'unico modo sicuro per risolvere le dipendenze circolari , poiché ogni volta il linker visita un archivio, inserisce (e registra i simboli non risolti di) solo i file oggetto che risolvono i simboli attualmente non risolti . Per questo motivo, l'algoritmo di CMake di ripetizione dei componenti collegati nel grafico delle dipendenze potrebbe occasionalmente fallire. (Vedi anche l'eccellente post sul blog di Ian Lance Taylor sui linker per maggiori dettagli.)
Jorgen,

3
La tua risposta mi ha aiutato a risolvere i miei errori di collegamento e tu hai spiegato molto chiaramente COME evitare problemi, ma hai idea PERCHÉ è stato progettato per funzionare in questo modo?
Anton Daneyko,

102

Il linker GNU ld è un cosiddetto linker intelligente. Terrà traccia delle funzioni utilizzate dalle precedenti librerie statiche, eliminando definitivamente quelle funzioni che non sono utilizzate dalle sue tabelle di ricerca. Il risultato è che se si collega una libreria statica troppo presto, le funzioni in quella libreria non saranno più disponibili per le librerie statiche successivamente sulla linea di collegamento.

Il tipico linker UNIX funziona da sinistra a destra, quindi metti tutte le tue librerie dipendenti a sinistra e quelle che soddisfano quelle dipendenze sulla destra della linea di collegamento. È possibile che alcune librerie dipendano da altre, mentre allo stesso tempo altre librerie dipendono da esse. Questo è dove diventa complicato. Quando si tratta di riferimenti circolari, correggi il tuo codice!


2
È qualcosa con solo gnu ld / gcc? O è qualcosa di comune con i linker?
Mike,

2
Apparentemente più compilatori Unix hanno problemi simili. MSVC non è del tutto libero da questi problemi, tuttavia, ma non sembrano essere così male.
MSalters,

4
Gli strumenti di sviluppo MS non tendono a mostrare questi problemi tanto perché se usi una catena di strumenti interamente MS finisce per impostare correttamente l'ordine dei linker e non noti mai il problema.
Michael Kohne,

16
Il linker MSVC è meno sensibile a questo problema perché cercherà in tutte le librerie un simbolo senza riferimento. L'ordine delle librerie può ancora influire sul simbolo che viene risolto se più di una libreria ha il simbolo. Da MSDN: "Anche le librerie vengono ricercate nell'ordine della riga di comando, con il seguente avvertimento: I simboli non risolti quando si porta un file oggetto da una libreria vengono prima cercati in quella libreria, quindi le seguenti librerie dalla riga di comando e / DEFAULTLIB (Specify Default Library) direttive e quindi a tutte le librerie all'inizio della riga di comando "
Michael Burr,

4
"... smart linker ..." - Credo che sia classificato come linker "single pass", non come "smart linker".
jww

54

Ecco un esempio per chiarire come funzionano le cose con GCC quando sono coinvolte le librerie statiche . Quindi supponiamo di avere il seguente scenario:

  • myprog.o- contenente la main()funzione, dipende dalibmysqlclient
  • libmysqlclient- statico, per il bene dell'esempio (preferiresti la libreria condivisa, ovviamente, poiché libmysqlclientè enorme); in /usr/local/lib; e dipende dalle cose dalibz
  • libz (dinamico)

Come colleghiamo questo? (Nota: esempi dalla compilazione su Cygwin usando 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

Se aggiungi -Wl,--start-group ai flag del linker non importa in quale ordine si trovano o se ci sono dipendenze circolari.

Su Qt questo significa aggiungere:

QMAKE_LFLAGS += -Wl,--start-group

Risparmia un sacco di tempo nei guai e non sembra rallentare molto il collegamento (che comunque richiede molto meno tempo della compilazione).


8

Un'altra alternativa sarebbe quella di specificare due volte l'elenco delle librerie:

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

In questo modo, non è necessario preoccuparsi della sequenza corretta poiché il riferimento verrà risolto nel secondo blocco.


5

È possibile utilizzare l'opzione -Xlinker.

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

è QUASI uguale a

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

Attento!

  1. L'ordine all'interno di un gruppo è importante! Ecco un esempio: una libreria di debug ha una routine di debug, ma la libreria non di debug ha una versione debole della stessa. È necessario inserire la libreria di debug PRIMA nel gruppo o si passerà alla versione non di debug.
  2. È necessario precedere ogni libreria nell'elenco dei gruppi con -Xlinker

5

Un suggerimento rapido che mi ha fatto scattare: se stai invocando il linker come "gcc" o "g ++", quindi usando "--start-group" e "--end-group" non passerai queste opzioni al linker - né segnalerà un errore. Fallirà il collegamento con simboli indefiniti se l'ordine della libreria è sbagliato.

Devi scriverli come "-Wl, - start-group" ecc. Per dire a GCC di passare l'argomento al linker.


2

L'ordine dei collegamenti è certamente importante, almeno su alcune piattaforme. Ho visto arresti anomali per le applicazioni collegate con le librerie nell'ordine sbagliato (dove errato significa che A è collegato prima di B ma B dipende da A).


2

L'ho visto molto, alcuni dei nostri moduli si collegano a oltre 100 librerie del nostro codice più sistema e librerie di terze parti.

A seconda dei diversi linker HP / Intel / GCC / SUN / SGI / IBM / etc è possibile ottenere funzioni / variabili non risolte ecc., Su alcune piattaforme è necessario elencare le librerie due volte.

Per la maggior parte usiamo una gerarchia strutturata di librerie, core, piattaforma, diversi livelli di astrazione, ma per alcuni sistemi devi ancora giocare con l'ordine nel comando link.

Una volta che hai trovato una soluzione, documentala in modo che lo sviluppatore successivo non debba risolverla di nuovo.

Il mio vecchio professore diceva " alta coesione e basso accoppiamento ", è ancora vero oggi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.