Чому порядок з'єднання бібліотек іноді викликає помилки в GCC?


Відповіді:


558

(Дивіться історію цієї відповіді, щоб отримати більш детальний текст, але зараз я думаю, що читачеві легше бачити реальні командні рядки).


Загальні файли, якими користуються всі команди нижче

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

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

$ cat d.cpp
int b;

Посилання на статичні бібліотеки

$ 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

Лінк шукає зліва направо і відзначає нерозв’язані символи. Якщо бібліотека розв’язує символ, для вирішення цього символу потрібні файли об'єктів цієї бібліотеки (в цьому випадку не входить libb.a).

Залежності статичних бібліотек одна від одної працюють однаково - спочатку повинна бути бібліотека, якій потрібні символи, потім бібліотека, яка розв’язує символ.

Якщо статична бібліотека залежить від іншої бібліотеки, але інша бібліотека знову залежить від колишньої бібліотеки, виникає цикл. Ви можете вирішити це шляхом додавання циклічно залежних бібліотек до -(та -), наприклад, як -( -la -lb -)(можливо, вам знадобиться уникнути паролів, таких як -\(і -\)). Потім лінкер кілька разів шукає ті вкладені вкладки, щоб забезпечити усунення залежностей від велосипедного руху. Крім того , ви можете вказати в бібліотеках кілька разів, так що кожен друг перед другом: -la -lb -la.

Посилання на динамічні бібліотеки

$ 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

Тут те саме - бібліотеки повинні слідувати об’єктним файлам програми. Різниця тут порівняно зі статичними бібліотеками полягає в тому, що вам не потрібно дбати про залежності бібліотек одна від одної, оскільки динамічні бібліотеки самі розбирають свої залежності .

У деяких останніх дистрибутивах, очевидно, за замовчуванням використовується --as-neededпрапор лінкера, який примушує, що файли об'єктів програми надходять до динамічних бібліотек. Якщо цей прапор буде передано, лінкер не посилатиметься на бібліотеки, які не потрібні виконуваному файлу (і він виявляє це зліва направо). Мій останній дистрибутив archlinux не використовує цей прапор за замовчуванням, тому він не видав помилку за недотримання правильного порядку.

Неправильно опускати залежність b.soпроти d.soпри створенні першої. aТоді вам потрібно буде вказати бібліотеку при посиланні , але aце дійсно не потребує самого цілого числа b, тому його не слід робити, щоб дбати про bвласні залежності.

Ось приклад наслідків, якщо ви пропустите вказати залежності 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"

Якщо ви зараз вивчите, які залежності мають бінарні файли, то зауважте, що саме бінарне також залежить від того libd, libbяк воно повинно. Бінарний файл потрібно буде повторно пов’язати, якщо libbпізніше це залежить від іншої бібліотеки, якщо ви зробите це так. І якщо хтось ще завантажує libbкористування dlopenпід час виконання (подумайте про завантаження плагінів динамічно), дзвінок також буде невдалим. Тож "right"справді має бути wrongтакож.


10
Повторюйте, доки всі символи не вирішені, так, ви думаєте, що вони можуть керувати топологічним родом. LLVM має 78 статичних бібліотек самостійно, з хто-хто-що залежить. Правда, у нього також є сценарій для визначення параметрів компіляції / посилання, але ви не можете використовувати це за будь-яких обставин.
Steve314

6
@Steve - це те, що роблять програми lorder+ tsort. Але іноді порядку немає, якщо у вас є циклічні посилання. Тоді вам просто доведеться переглядати список бібліотек, поки все не буде вирішено.
Йоханнес Шауб - ліб

10
@Johannes - Визначте максимально сильно пов'язані компоненти (наприклад, алгоритм Тар'янаса), а потім топологічно сортуйте (за своєю суттю нециклічний) диграф компонентів. Кожен компонент може розглядатися як одна бібліотека - якщо потрібна якась одна бібліотека з компонента, цикл (и) залежності призведе до необхідності всіх бібліотек цього компонента. Так що ні, насправді немає необхідності переглядати всі бібліотеки, щоб вирішити все, і немає необхідності в незручних параметрах командного рядка - один метод, що використовує два відомих алгоритми, може правильно обробляти всі випадки.
Steve314

4
Я хотів би додати одну важливу деталь до цієї чудової відповіді: Використання "- (архівів -)" або "- архівів стартової групи --end-group" - єдиний надійний спосіб вирішення кругових залежностей , оскільки кожен раз лінкер відвідує архів, він залучає (і реєструє невирішені символи) лише об'єктні файли, які вирішують поточні невирішені символи . Через це алгоритм CMake повторення підключених компонентів у графіку залежності може періодично виходити з ладу. (Детальніше див. У відмінній публікації блогу Ієна Ланса Тейлора про посиланнях .)
jorgen

3
Ваша відповідь допомогла мені вирішити мої помилки у зв’язуванні, і ви дуже чітко пояснили, ЯК уникнути неполадок, але чи маєте ви уявлення, ЧОМУ це було розроблено так?
Антон Данейко

102

Лінкер GNU ld - це так званий розумний лінкер. Він буде відслідковувати функції, використовувані попередніми статичними бібліотеками, постійно викидаючи ті функції, які не використовуються зі своїх таблиць пошуку. Результат полягає в тому, що якщо ви зв’яжете статичну бібліотеку занадто рано, то функції цієї бібліотеки більше не доступні статичним бібліотекам пізніше у рядку посилань.

Типовий лінкер UNIX працює зліва направо, тому поставте всі залежні бібліотеки зліва та ті, які задовольняють ці залежності праворуч від лінії зв'язку. Ви можете виявити, що деякі бібліотеки залежать від інших, тоді як інші бібліотеки залежать від них. Тут це ускладнюється. Якщо мова йде про кругові посилання, виправте свій код!


2
Це щось із лише gnu ld / gcc? Або це щось спільне з лінкерами?
Майк,

2
Мабуть, більше компіляторів Unix мають подібні проблеми. MSVC не зовсім вільний від цих питань, наприклад, але вони, здається, не такі вже й погані.
MSalters

4
Засоби розробки MS не мають тенденції показувати ці проблеми так сильно, тому що якщо ви використовуєте ланцюг інструментів all-MS, це закінчується правильним налаштуванням порядку компонування, і ви ніколи не помічаєте проблеми.
Майкл Коне

16
Лінк MSVC менш чутливий до цього питання, тому що він буде шукати у всіх бібліотеках невідредагований символ. Порядок бібліотеки все ще може вплинути на те, який символ буде вирішений, якщо символ має більше ніж одна бібліотека. Від MSDN: "Бібліотеки також шукаються в порядку командного рядка із наступним застереженням: У цій бібліотеці спочатку шукаються символи, які не є вирішеними при приведенні об'єктного файлу з бібліотеки, а потім наступні бібліотеки з командного рядка та / DEFAULTLIB (Вкажіть бібліотеку за замовчуванням), а потім до будь-яких бібліотек на початку командного рядка "
Майкл Берр

4
"... розумний лінкер ..." - Я вважаю, що це класифікується як "однопрохідний" лінкер, а не "розумний лінкер".
jww

54

Ось приклад, щоб зрозуміти, як все працює з GCC, коли задіяні статичні бібліотеки. Тож припустимо, що у нас є такий сценарій:

  • myprog.o- містить main()функцію, залежно відlibmysqlclient
  • libmysqlclient- статичні, для прикладу (ви б, звичайно, віддавали перевагу спільній бібліотеці, оскільки libmysqlclientвеличезна кількість); в /usr/local/lib; і залежать від речей відlibz
  • libz (динамічний)

Як ми це пов’язуємо? (Примітка: приклади компіляції на Cygwin за допомогою 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

Якщо ви додасте -Wl,--start-groupдо прапорців посилання, не важливо, в якому порядку вони перебувають, чи є кругові залежності.

На Qt це означає додавання:

QMAKE_LFLAGS += -Wl,--start-group

Економить навантажень часу, що заплутується, і, схоже, це не сповільнить багато зв'язків (що займає набагато менше часу, ніж компіляція).


8

Іншою альтернативою було б вказати список бібліотек двічі:

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

Для цього вам не доведеться турбуватися з правильною послідовністю, оскільки посилання буде вирішена у другому блоці.


5

Ви можете використовувати опцію -Xlinker.

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

дорівнює АЛМОСТ

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

Обережно!

  1. Порядок у групі важливий! Ось приклад: у бібліотеці налагодження є програма налагодження, але у бібліотеки, що не налагоджує, є слабка версія такої ж. Ви повинні поставити бібліотеку налагодження FIRST у групі, або ви перейдете до не-налагоджуваної версії.
  2. Потрібно передувати кожній бібліотеці у списку груп за допомогою -Xlinker

5

Швидкий підказок, який підключив мене: якщо ви посилаєтеся на посилання як "gcc" або "g ++", то використання "--start-group" і "--end-group" не передасть ці параметри до Linker - і він не позначить помилку. Він просто провалить посилання з невизначеними символами, якщо ви неправильно замовили бібліотеку.

Вам потрібно записати їх як "-Wl, - start-group" тощо, щоб сказати GCC передати аргумент до лінкера.


2

Порядок посилання, безумовно, має значення, принаймні, на деяких платформах. Я бачив збої в додатках, пов’язаних з бібліотеками в неправильному порядку (де неправильний означає A, пов’язаний перед B, але B залежить від A).


2

Я бачив це багато, деякі з наших модулів посилаються на понад 100 бібліотек нашого коду, плюс система та сторонні бібліотеки.

Залежно від різних лінкерів HP / Intel / GCC / SUN / SGI / IBM / тощо, ви можете отримати невирішені функції / змінні тощо, на деяких платформах вам доведеться перераховувати бібліотеки двічі.

Здебільшого ми використовуємо структуровану ієрархію бібліотек, ядра, платформи, різних шарів абстракції, але для деяких систем вам все одно доводиться грати з порядком у команді посилання.

Після того, як ви натрапите на документ рішення, його наступному розробнику не доведеться працювати над ним знову.

Мій старий викладач говорив: " висока згуртованість і низька зв'язок ", це все ще актуально сьогодні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.