Dlaczego kolejność łączenia bibliotek czasami powoduje błędy w GCC?


Odpowiedzi:


558

(Zobacz historię tej odpowiedzi, aby uzyskać bardziej skomplikowany tekst, ale myślę, że teraz czytelnikowi łatwiej jest zobaczyć prawdziwe wiersze poleceń).


Wspólne pliki współdzielone przez wszystkie poniższe polecenia

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

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

$ cat d.cpp
int b;

Łączenie z bibliotekami statycznymi

$ 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

Linker wyszukuje od lewej do prawej i zauważa nierozstrzygnięte symbole. Jeśli biblioteka rozwiązuje symbol, pliki obiektowe tej biblioteki muszą rozwiązać symbol (w tym przypadku bo z libb.a).

Zależności między bibliotekami statycznymi działają tak samo - biblioteka, która potrzebuje symboli, musi być pierwsza, a następnie biblioteka, która rozwiązuje symbol.

Jeśli biblioteka statyczna zależy od innej biblioteki, ale druga biblioteka ponownie zależy od poprzedniej biblioteki, następuje cykl. Możesz rozwiązać ten problem, dołączając biblioteki zależne cyklicznie przez -(i -), np. -( -la -lb -)(Może być konieczne ucieczkę przed parenami, takimi jak -\(i -\)). Linker przeszukuje te załączone biblioteki wiele razy, aby upewnić się, że zależności cykliczne zostały rozwiązane. Alternatywnie, można określić bibliotekach wiele razy, więc każdy ma przed sobą: -la -lb -la.

Łączenie z bibliotekami dynamicznymi

$ 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

Tutaj jest tak samo - biblioteki muszą podążać za plikami obiektowymi programu. Różnica w porównaniu z bibliotekami statycznymi polega na tym, że nie musisz przejmować się wzajemnymi zależnościami bibliotek , ponieważ biblioteki dynamiczne same uporządkują ich zależności .

Niektóre najnowsze dystrybucje najwyraźniej domyślnie używają --as-neededflagi linkera, która wymusza, aby pliki obiektowe programu znajdowały się przed bibliotekami dynamicznymi. Jeśli ta flaga zostanie przekazana, linker nie będzie łączył się z bibliotekami, które w rzeczywistości nie są potrzebne plikowi wykonywalnemu (i wykrywa to od lewej do prawej). Moja ostatnia dystrybucja archlinux domyślnie nie używa tej flagi, więc nie dała błędu za nieprzestrzeganie prawidłowej kolejności.

To nie jest poprawna pominąć zależność b.sowobec d.sopodczas tworzenia tej pierwszej. Będziesz wtedy musiał określić bibliotekę podczas łączenia a, ale atak naprawdę nie potrzebuje bsamej liczby całkowitej , więc nie należy dbać o bwłasne zależności.

Oto przykład implikacji, jeśli pominiesz określenie zależności 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"

Jeśli spojrzysz teraz na zależności, jakie ma plik binarny, zauważysz, że sam plik binarny zależy również od libd, a nie tylko libbtak, jak powinien. Plik binarny będzie musiał zostać ponownie połączony, jeśli libbpóźniej zależy od innej biblioteki, jeśli zrobisz to w ten sposób. A jeśli ktoś inny ładuje libbprzy użyciu dlopenw czasie wykonywania (pomyśl o dynamicznym ładowaniu wtyczek), wywołanie również się nie powiedzie. Więc tak "right"naprawdę powinno być wrong.


10
Powtarzaj, aż wszystkie symbole zostaną rozwiązane, eh - można by pomyśleć, że mogą zarządzać rodzajem topologicznym. LLVM ma 78 własnych bibliotek statycznych z zależnościami kto-co-wie. To prawda, że ​​ma również skrypt do określania opcji kompilacji / łącza - ale nie można go używać we wszystkich okolicznościach.
Steve314,

6
@ Steve właśnie to robią programy lorder+ tsort. Ale czasami nie ma porządku, jeśli masz cykliczne odniesienia. Następnie wystarczy przejrzeć listę bibliotek, aż wszystko zostanie rozwiązane.
Johannes Schaub - litb

10
@Johannes - Określ maksymalne silnie połączone komponenty (np. Algorytm Tarjansa), a następnie sortuj topologicznie (z natury niecykliczny) digraf elementów. Każdy komponent może być traktowany jako jedna biblioteka - jeśli potrzebna jest jakakolwiek biblioteka z komponentu, cykle zależności spowodują, że wszystkie biblioteki w tym komponencie będą potrzebne. Więc nie, naprawdę nie ma potrzeby przechodzenia między wszystkimi bibliotekami w celu rozwiązania wszystkiego i nie ma potrzeby stosowania niewygodnych opcji wiersza poleceń - jedna metoda wykorzystująca dwa dobrze znane algorytmy może poprawnie obsłużyć wszystkie przypadki.
Steve314,

4
Chciałbym dodać jeden ważny szczegół do tej doskonałej odpowiedzi: użycie „- (archiwa -)” lub „--start-group archives --end-group” to jedyny pewny sposób rozwiązania zależności cyklicznych , ponieważ za każdym razem linker odwiedza archiwum, pobiera (i rejestruje nierozwiązane symbole) tylko pliki obiektowe, które rozwiązują aktualnie nierozwiązane symbole . Z tego powodu algorytm CMake powtarzania połączonych komponentów na wykresie zależności może czasami zawieść. (Zobacz także doskonały post Iana Lance'a Taylora na
linkerach,

3
Twoja odpowiedź pomogła mi rozwiązać moje błędy linkowania i bardzo jasno wyjaśniłeś, JAK uniknąć kłopotów, ale czy masz pojęcie DLACZEGO został zaprojektowany tak, aby działał w ten sposób?
Anton Daneyko

102

Linker GNU ld to tak zwany inteligentny linker. Będzie śledził funkcje używane przez poprzednie biblioteki statyczne, trwale odrzucając te funkcje, które nie są używane z jego tabel odnośników. W rezultacie, jeśli podłączysz bibliotekę statyczną zbyt wcześnie, funkcje w tej bibliotece nie będą już dostępne dla bibliotek statycznych później w linii łącza.

Typowy linker UNIX działa od lewej do prawej, więc umieść wszystkie zależne biblioteki po lewej, a te, które spełniają te zależności, po prawej stronie linii łącza. Może się okazać, że niektóre biblioteki zależą od innych, a jednocześnie inne biblioteki od nich zależą. To się komplikuje. Jeśli chodzi o odwołania cykliczne, napraw swój kod!


2
Czy to coś z tylko gnu ld / gcc? Czy jest to coś wspólnego z linkerami?
Mike

2
Najwyraźniej więcej kompilatorów uniksowych ma podobne problemy. MSVC nie jest całkowicie wolny od tych problemów, ale nie wydają się one takie złe.
MSalters

4
Narzędzia deweloperskie MS nie pokazują tych problemów tak często, ponieważ jeśli używasz łańcucha narzędziowego MS, kończy się to prawidłowym ustawieniem kolejności linkerów i nigdy nie zauważysz problemu.
Michael Kohne,

16
Linker MSVC jest mniej wrażliwy na ten problem, ponieważ przeszuka wszystkie biblioteki w poszukiwaniu niereferencyjnego symbolu. Kolejność bibliotek nadal może wpływać na to, który symbol zostanie rozwiązany, jeśli symbol ma więcej niż jedna biblioteka. Z MSDN: „Biblioteki są również przeszukiwane w kolejności wiersza poleceń, z następującym zastrzeżeniem: Symbole, które nie są rozwiązane podczas pobierania pliku obiektu z biblioteki, są najpierw wyszukiwane w tej bibliotece, a następnie następujące biblioteki z wiersza poleceń i / DEFAULTLIB (Określ domyślną bibliotekę), a następnie do dowolnych bibliotek na początku wiersza poleceń ”
Michael Burr

4
„... inteligentny linker ...” - Uważam, że jest klasyfikowany jako linker „jednoprzebiegowy”, a nie „inteligentny linker”.
jww

54

Oto przykład, aby wyjaśnić, jak działa GCC w przypadku bibliotek statycznych . Załóżmy więc, że mamy następujący scenariusz:

  • myprog.o- zawierające main()funkcję, zależną odlibmysqlclient
  • libmysqlclient- statyczny, na przykład (wolisz oczywiście bibliotekę współdzieloną, ponieważ libmysqlclientjest ogromna); w /usr/local/lib; i zależy od rzeczy zlibz
  • libz (dynamiczny)

Jak to połączyć? (Uwaga: przykłady z kompilacji na Cygwin przy użyciu 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

Jeśli dodasz -Wl,--start-groupdo flag linkera, nie ma znaczenia, w jakiej są kolejności lub czy istnieją zależności cykliczne.

W Qt oznacza to dodanie:

QMAKE_LFLAGS += -Wl,--start-group

Oszczędza mnóstwo czasu na bałaganie i wydaje się, że nie spowalnia dużo linkowania (co i tak zajmuje znacznie mniej czasu niż kompilacja).


8

Inną alternatywą byłoby dwukrotne określenie listy bibliotek:

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

Robiąc to, nie musisz zawracać sobie głowy właściwą sekwencją, ponieważ odniesienie zostanie rozwiązane w drugim bloku.


5

Możesz użyć opcji -Xlinker.

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

jest PRAWIE równy

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

Ostrożnie!

  1. Kolejność w grupie jest ważna! Oto przykład: biblioteka debugowania ma procedurę debugowania, ale biblioteka bez debugowania ma jej słabą wersję. Musisz umieścić bibliotekę debugowania PIERWSZE w grupie, w przeciwnym razie przejdziesz do wersji bez debugowania.
  2. Musisz poprzedzić każdą bibliotekę na liście grup -Xlinker

5

Szybka wskazówka, która mnie zaskoczyła: jeśli wywołujesz linker jako „gcc” lub „g ++”, to użycie „--start-group” i „--end-group” nie przekaże tych opcji do linker - nie będzie też oznaczać błędu. To po prostu zawiedzie łącze z niezdefiniowanymi symbolami, jeśli źle zrobisz porządek w bibliotece.

Musisz napisać je jako „-Wl, - start-group” itp., Aby powiedzieć GCC, aby przekazał argument do linkera.


2

Kolejność linków na pewno ma znaczenie, przynajmniej na niektórych platformach. Widziałem awarie aplikacji powiązanych z bibliotekami w niewłaściwej kolejności (gdzie zła oznacza A połączone przed B, ale B zależy od A).


2

Widziałem to bardzo często, niektóre nasze moduły łączą ponad 100 bibliotek naszego kodu oraz bibliotek systemowych i zewnętrznych.

W zależności od różnych linkerów HP / Intel / GCC / SUN / SGI / IBM / itp. Możesz uzyskać nierozwiązane funkcje / zmienne itp., Na niektórych platformach musisz dwukrotnie wyświetlić biblioteki.

W przeważającej części używamy strukturalnej hierarchii bibliotek, rdzenia, platformy, różnych warstw abstrakcji, ale w niektórych systemach nadal musisz grać w kolejności w poleceniu link.

Po znalezieniu dokumentu rozwiązania, aby następny programista nie musiał go ponownie opracowywać.

Mój dawny wykładowca zwykł mawiać: „ wysoka kohezja i niskie sprzężenie ”, to jest do dzisiaj prawdą.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.