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

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

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


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

 $ 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 bo $ ar cr libb.a bo $ g++ -c d.cpp -o do $ ar cr libd.a do $ 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 

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

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

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

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

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

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

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

 QMAKE_LFLAGS += -Wl,--start-group 

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

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

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

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

Вы можете использовать опцию -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 

Осторожный !

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

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

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

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

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

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

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

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

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

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

  • ошибка LNK2019: неразрешенный внешний символ _main, указанный в функции ___tmainCRTStartup
  • Ошибки компоновщика при компиляции против glib ...?
  • Что означает «статически связанное» и «динамически связанное»?
  • Могу ли я смешивать статические и общедоступные библиотеки при связывании?
  • ошибка LNK2038: обнаружено несоответствие для '_ITERATOR_DEBUG_LEVEL': значение '0' не соответствует значению '2' в main.obj
  • Как принудительно включить определение «неиспользуемых» объектов в библиотеке
  • VA (виртуальный адрес) и RVA (относительный виртуальный адрес)
  • Что такое опция -fPIE для независимых по позиции исполняемых файлов в gcc и ld?
  • Давайте будем гением компьютера.