Почему порядок, в котором связаны библиотеки, иногда вызывает ошибки в 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

Компоновщик выполняет поиск слева направо и отмечает неразрешенные символы. Если библиотека разрешает символ, она принимает объектные файлы этой библиотеки для разрешения символа (в данном случае bo из 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
@ Это то, что делают программы lorder+ tsort. Но иногда нет порядка, если у вас есть циклические ссылки. Тогда вам просто нужно перебрать список библиотек, пока все не будет решено.
Йоханнес Шауб -

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

4
Я хотел бы добавить одну важную деталь к этому превосходному ответу: использование «- (archives -)» или «--start-group archives --end-group» является единственным верным способом разрешения циклических зависимостей , поскольку каждый раз компоновщик посещает архив, он извлекает (и регистрирует неразрешенные символы) только объектные файлы, которые разрешают неразрешенные символы . Из-за этого алгоритм CMake повторения связанных компонентов в графе зависимостей может иногда давать сбой. (См. Также превосходное сообщение в блоге Яна Лэнса Тейлора о компоновщиках для более подробной информации.)
Йорген

3
Ваш ответ помог мне исправить мои ошибки в ссылках, и вы очень четко объяснили, КАК избежать неприятностей, но знаете ли вы, ПОЧЕМУ это было разработано, чтобы работать таким образом?
Антон Данейко

102

GNU ld linker - это так называемый умный линкер. Он будет отслеживать функции, используемые предыдущими статическими библиотеками, постоянно отбрасывая те функции, которые не используются из его справочных таблиц. В результате, если вы связываете статическую библиотеку слишком рано, то функции в этой библиотеке больше не будут доступны статическим библиотекам позже в строке ссылки.

Типичный компоновщик UNIX работает слева направо, поэтому поместите все зависимые библиотеки слева, а те, которые удовлетворяют этим зависимостям, справа от строки ссылки. Вы можете обнаружить, что некоторые библиотеки зависят от других, в то же время другие библиотеки зависят от них. Это где это становится сложным. Когда дело доходит до циклических ссылок, исправьте свой код!


2
Это что-то только с GNU LD / GCC? Или это что-то общее с линкерами?
Майк

2
Видимо, больше компиляторов Unix имеют похожие проблемы. Эй, MSVC не совсем свободен от этих проблем, но они не так уж и плохи.
MSalters

4
Средства разработки MS не склонны показывать эти проблемы, потому что, если вы используете цепочку инструментов полностью MS, это приводит к правильной настройке порядка компоновщика, и вы никогда не заметите проблему.
Майкл Кохн

16
Компоновщик MSVC менее чувствителен к этой проблеме, потому что он будет искать во всех библиотеках несвязанный символ. Порядок библиотек все еще может влиять на то, какой символ будет разрешен, если более чем у одной библиотеки есть символ. Из MSDN: «Поиск в библиотеках также производится в порядке командной строки со следующим предупреждением: символы, которые не были разрешены при получении объектного файла из библиотеки, сначала ищутся в этой библиотеке, а затем следующие библиотеки из командной строки и / DEFAULTLIB (указать библиотеку по умолчанию), а затем любые библиотеки в начале командной строки "
Michael Burr

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» не передаст эти параметры компоновщик - и при этом он не будет отмечать ошибку. Он просто потерпит неудачу при ссылке с неопределенными символами, если вы неправильно указали порядок в библиотеке.

Вам нужно написать их как "-Wl, - start-group" и т. Д., Чтобы GCC передал аргумент компоновщику.


2

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


2

Я видел это много, некоторые из наших модулей связывают более 100 библиотек нашего кода плюс системные и сторонние библиотеки.

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

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

Как только вы натолкнетесь на документ решения, чтобы следующему разработчику не пришлось снова его разрабатывать.

Мой старый лектор говорил: « высокая сплоченность и низкая связь », это все еще верно сегодня.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.