Почему порядок, в котором связаны библиотеки, иногда вызывает ошибки в GCC? - PullRequest
414 голосов
/ 05 сентября 2008

Почему порядок, в котором связаны библиотеки, иногда вызывает ошибки в GCC?

Ответы [ 9 ]

524 голосов
/ 03 января 2009

(см. Историю этого ответа, чтобы получить более сложный текст, но теперь я думаю, что читателю легче увидеть реальные командные строки).


Общие файлы, используемые всеми командами ниже

$ 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

Компоновщик выполняет поиск слева направо и отмечает неразрешенные символы. Если библиотека разрешает символ, она принимает объектные файлы этой библиотеки для разрешения символа (в данном случае b.o из 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.

95 голосов
/ 03 января 2009

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

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

51 голосов
/ 16 июля 2011

Вот пример, чтобы прояснить, как работают 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
25 голосов
/ 05 апреля 2015

Если вы добавите -Wl,--start-group к флагам компоновщика, все равно, в каком порядке они находятся, или существуют циклические зависимости.

В Qt это означает добавление:

QMAKE_LFLAGS += -Wl,--start-group

Экономит кучу времени, тратя время на компоновку, и, похоже, не сильно замедляет линковку (в любом случае это занимает гораздо меньше времени, чем компиляция).

8 голосов
/ 21 марта 2014

Другой вариант - дважды указать список библиотек:

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

При этом вам не нужно беспокоиться о правильной последовательности, поскольку ссылка будет разрешена во втором блоке.

5 голосов
/ 09 августа 2011

Вы можете использовать опцию -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. Порядок в группе важен! Вот пример: библиотека отладки имеет подпрограмму отладки, но не отладочную Библиотека имеет слабую версию того же самого. Вы должны поставить библиотеку отладки ПЕРВЫЙ в группе, или вы перейдете к неотладочной версии.
  2. Вам необходимо предшествовать каждой библиотеке в списке групп с -Xlinker
4 голосов
/ 11 августа 2013

Быстрый совет, который меня смутил: если вы вызываете компоновщик как "gcc" или "g ++", то использование "--start-group" и "--end-group" не пропустит эти параметры через компоновщик - и он не будет отмечать ошибку. Он просто потерпит неудачу при ссылке с неопределенными символами, если вы неправильно указали библиотечный порядок.

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

3 голосов
/ 05 сентября 2008

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

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

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

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

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

2 голосов
/ 28 апреля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...