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

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

Ответ 1

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


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

$ 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 -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, который заставляет программные объектные файлы --as-needed перед динамическими библиотеками. Если этот флаг передан, компоновщик не будет ссылаться на библиотеки, которые на самом деле не нужны исполняемому файлу (и он обнаруживает это слева направо). Мой недавний дистрибутив archlinux по умолчанию не использует этот флаг, поэтому он не выдает ошибку за несоблюдение правильного порядка.

b.so опускать зависимость 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, вы заметите, что сам двоичный файл зависит также от libd, а не только от libb как и должно быть. Бинарный файл необходимо будет заново связать, если libb позже будет зависеть от другой библиотеки, если вы сделаете это таким образом. И если кто - то загружает libb с помощью dlopen во время выполнения (думаю загрузки плагинов динамически), то вызов будет не в состоянии, а также. Так что "right" действительно должно быть и wrong.

Ответ 2

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

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

Ответ 3

Здесь приведен пример, чтобы понять, как все работает с 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

Ответ 4

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

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

QMAKE_LFLAGS += -Wl,--start-group

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

Ответ 5

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

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

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

Ответ 6

Вы можете использовать опцию -Xlinker.

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

ALMOST равно

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

Осторожно!

  • Порядок внутри группы важен! Вот пример: библиотека отладки имеет процедуру отладки, но не отладка библиотека имеет слабую версию. Вы должны поместить библиотеку отладки FIRST в группе или вы решите на не-отладочную версию.
  • Вам необходимо предшествовать каждой библиотеке в списке групп с помощью -Xlinker

Ответ 7

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

Ответ 10

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