2.3.1. Архивы
Архив (или статическая библиотека) — это коллекция объектных файлов, хранящаяся в виде одного файла (он является примерным эквивалентом LIB-файла в Windows). Когда архив поступает на вход компоновщика, тот ищет в нем нужные объектные файлы, извлекает их и подключает к программе так, как если бы они были указаны непосредственно.
Архив создается посредством команды ar. Архивные файлы традиционно имеют расширение .a, а не .o, которое закреплено за отдельными объектными файлами. Вот как объединить файлы test1.o и test2.o в единый архив libtest.a:
% ar cr libtest.a test1.o test2.o
Флаги cr сообщают команде ar о необходимости создать архив.[8] Теперь можно подключать этот архив к программам с помощью флага -ltest компилятора gcc или g++, как описывалось в разделе 1.2.2, "Компоновка объектных файлов".
Обнаруживая в командной строке архив, компоновщик ищет в нем определения всех символических констант (функций или переменных), на которые дается ссылка в уже обработанных объектных файлах. Объектные файлы, содержащие определения этих констант, извлекаются из архива и включаются в исполняемый файл. В связи с тем что компоновщик просматривает архив один раз, архивные файлы нужно указывать в конце командной строки. Предположим, например, что имеются два файла: test.c (листинг 2.7) и app.c (листинг 2.8).
Листинг 2.7. (
test.c) Первый исходный файл
int f() {
return 3;
}
Листинг 2.8. (
app.c) Второй исходный файл
int main() {
return f();
}
Теперь допустим, что файл test.o включен вместе с другими объектными файлами в архив libtest.a. Тогда следующая команда не будет работать:
% gcc -о app -L. -ltest app.о
app.о: In function 'main':
app.о(.text+0x4): undefined reference to 'f'
collect2: ld returned 1 exit status
Как следует из сообщения об ошибке, несмотря на то что файл libtest.а содержит определение функции f(), компоновщик не нашел ее. Это объясняется тем. что компоновщик анализирует свои аргументы последовательно, слева направо, просматривая архив сразу же, как только он встречается в командной строке. На тот момент компоновщик еще не знал, что в дальнейшем ему встретится ссылка на функцию f(). Если сделать небольшую перестановку, все заработает:
% gcc -о app арр.о -L. -ltest
Теперь наличие в файле app.о ссылки на функцию f() заставляет компоновщик включить в программу объектный файл test.o из архива libtest.а.
2.3.2. Совместно используемые библиотеки
Совместно используемая библиотека (известная также как динамически подключаемая библиотека) напоминает архив тем, что она представляет собой группу объектных файлов. Но между ними есть ряд важных различий. Самое основное из них заключается в том, что, когда совместно используемая библиотека подключается к программе, в исполняемый файл не включается код самой библиотеки: в нем присутствует лишь ссылка на библиотеку. Если с несколькими программами компонуется одна и та же библиотека, все они будут ссылаться на нее, но ни в одну из них она не будет включена. Так расшифровывается термин "совместное использование".
Второе важное отличие состоит в том, что совместно используемая библиотека — это не просто коллекция объектных файлов, из которых компоновщик выбирает требуемый для разрешения ссылки. В данном случае все объектные файлы, входящие в библиотеку, объединяются в единый объектный файл. Благодаря этому программы, компонующиеся вместе с библиотекой, всегда имеют доступ ко всему ее содержимому, а не только к одной конкретной части.
Чтобы создать совместно используемую библиотеку, нужно сначала скомпилировать составляющие ее объектные файлы с указанием опции -fPIC, например:
% gcc -с -fPIC test1.c
Опция -fPIC сообщает компилятору о том, что файл test1.o станет частью совместно используемой библиотеки.
Позиционно-независимый код
Аббревиатура PIC (Position-Independent Code) в названии опции расшифровывается как "позиционно-независимый код". Функции в совместно используемой библиотеке могут загружаться по разным адресам разными программами, поэтому код библиотеки не должен зависеть от адреса (или позиции), по которому она загружена. Все это никак не касается программистов, просто нужно не забывать указывать флаг -fPIC при компиляции файлов, которые могут включаться в совместно используемую библиотеку.
Затем следует объединить объектные файлы в библиотеку:
% gcc -shared -fPIC -о libtest.so test1.o test2.o
Опция -shared заставляет компоновщик создать совместно используемую библиотеку, а не обычный исполняемый файл. Такие библиотеки имеют расширение .so. Подобно статическому архиву, имя библиотеки всегда начинается с префикса lib, указывающего на то. что файл является библиотекой.
Компоновка совместно используемой библиотеки аналогична компоновке архива. Например, следующая команда подключает к программе файл libtest.so, если он находится в текущем каталоге или одном из стандартных системных библиотечных каталогов:
% gcc -о app арр.о -L. ltest
Предположим, имеются оба файла: libtest.а и libtest.so. Каким образом компоновщик принимает решение? Он просматривает каждый заданный каталог (сначала те, что указаны в опции -L, затем стандартные) и, как только обнаруживает хотя бы один из файлов, тут же прекращает поиск. Если в найденном каталоге присутствует только один из файлов, он и выбирается. В противном случае выбор делается в пользу совместно используемой библиотеки, если явно не указано обратное. Отдать приоритет статическому архиву позволяет опция -static. Например, следующая команда подключит к программе архив libtest.a, даже если присутствует библиотека libtest.so:
% gcc -static -о app арр.о -L. -ltest
Команда ldd выводит список совместно используемых библиотек, подключенных к заданному исполняемому файлу. Все они должны быть доступны при запуске программы. Обратите внимание на то, что команда ldd сообщает о наличии дополнительной библиотеки: ld-linux.so. Она является частью механизма динамической компоновки в Linux.
Переменная LD_LIBRARY_PATH
Когда к программе подключается совместно используемая библиотека, компоновщик помещает в исполняемый файл ссылку на нее, но в этой ссылке указан не полный путь к библиотеке, а только имя файла. При запуске программы система сама находит библиотеку и загружает ее. По умолчанию система просматривает лишь каталоги /lib и /usr/lib. Если библиотека находится в другом каталоге, она не будет найдена и система откажется загружать программу.
Одно из решений заключается в компоновке программы с указанием флага -Wl,-rpath:
% gcc -о app арр.о -L. -ltest -Wl,-rpath,/usr/local/lib
Теперь в случае запуска программы app система будет искать требуемые библиотеки также в каталоге /usr/local/lib.
Но есть и другое решение: устанавливать переменную LD_LIBRARY_PATH при запуске программы. Подобно переменной среды PATH, переменная LD_LIBRARY_PATH представляет собой разделенный двоеточиями список каталогов. Если, к примеру, она равна /usr/local/lib:/opt/lib, то каталоги /usr/local/lib и /opt/lib будут просматриваться перед стандартными каталогами /lib и /usr/lib. Необходимо также учитывать, что при наличии данной переменной компоновщик будет просматривать заданные в ней каталоги, обнаруживая опцию -L в командной строке.[9]
2.3.3. Стандартные библиотеки
Даже если при компоновке программы не были заданы библиотеки, все равно одна из них почти наверняка присутствует. Дело в том, что компилятор gcc автоматически подключает к программе стандартную библиотеку языка С: libc. В нее, однако, не входят математические функции. Они находятся в отдельной библиотеке, libm, которую нужно компоновать явно. Например, чтобы скомпилировать и скомпоновать программу compute, использующую тригонометрические функции (такие как sin() и cos()), необходимо задать следующую команду:
% gcc -о compute compute.c -lm
При компоновке программ, написанных на C++, компилятор c++ или g++ автоматически подключает к ним стандартную библиотек языка C++: libstdc++.
2.3.4. Зависимости между библиотеками
Библиотеки часто связаны одна с другой. Например, во многих Linux-системах есть библиотека libtiff, содержащая функции чтения и записи графических файлов формата TIFF. Она, в свою очередь, использует библиотеки libjpeg (подпрограммы обработки JPEG-изображений) и libz (подпрограммы сжатия).