Почему порядок, в котором связаны библиотеки, иногда вызывает ошибки в GCC?
Почему порядок, в котором связаны библиотеки, иногда вызывает ошибки в GCC?
10 ответов
(Посмотрите историю этого ответа, чтобы получить более сложный текст, но теперь я думаю, что читателю легче увидеть реальные командные строки).
Общие файлы, используемые всеми командами ниже
$ 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 -)
(вам может понадобиться избежать Parens, таких как -\(
а также -\)
). Затем компоновщик несколько раз просматривает эти вложенные библиотеки, чтобы убедиться, что циклические зависимости разрешены. В качестве альтернативы, вы можете указывать библиотеки несколько раз, так что каждая из них стоит одна перед другой: -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
также.
GNU ld linker - это так называемый умный линкер. Он будет отслеживать функции, используемые предыдущими статическими библиотеками, постоянно выбрасывая те функции, которые не используются из его справочных таблиц. В результате, если вы связываете статическую библиотеку слишком рано, то функции в этой библиотеке больше не будут доступны статическим библиотекам позже в строке ссылки.
Типичный компоновщик 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
При этом вам не нужно беспокоиться о правильной последовательности, так как ссылка будет разрешена во втором блоке.
Быстрый совет, который меня смутил: если вы вызываете компоновщик как "gcc" или "g++", то использование "--start-group" и "--end-group" не передаст эти параметры компоновщик - и при этом он не будет отмечать ошибку. Он просто потеряет связь с неопределенными символами, если у вас неправильный порядок в библиотеке.
Вам нужно написать их как "-Wl, - start-group" и т. Д., Чтобы GCC передал аргумент компоновщику.
Вы можете использовать опцию -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
Осторожный!
- Порядок в группе важен! Вот пример: у библиотеки отладки есть подпрограмма отладки, но у библиотеки без отладки есть слабая версия той же самой. Вы должны поместить отладочную библиотеку FIRST в группу, иначе вы перейдете к неотладочной версии.
- Вам необходимо предшествовать каждой библиотеке в списке групп с -Xlinker
Я видел это много, некоторые из наших модулей связывают более 100 библиотек нашего кода плюс системные и сторонние библиотеки.
В зависимости от разных компоновщиков HP/Intel/GCC/SUN/SGI/IBM/ и т. Д. Вы можете получить неразрешенные функции / переменные и т. Д., На некоторых платформах вам придется дважды перечислять библиотеки.
По большей части мы используем структурированную иерархию библиотек, ядра, платформы, разные уровни абстракции, но для некоторых систем вам все равно придется играть с порядком в команде link.
Как только вы натолкнетесь на документ решения, чтобы следующему разработчику не пришлось снова его разрабатывать.
Мой старый лектор говорил: "высокая сплоченность и низкая связь", это все еще верно сегодня.
Порядок ссылок, безусловно, имеет значение, по крайней мере, на некоторых платформах. Я видел сбои для приложений, связанных с библиотеками в неправильном порядке (где неправильный означает A, связанный перед B, но B зависит от A).
Я думаю, это потому, что некоторые из этих библиотек имеют зависимости от других библиотек, и если они еще не были связаны, вы получите ошибки компоновщика.