Один из самых полезных инструментов в современных вычислениях: библиотеки. Что такое библиотека? Библиотека — это набор полезных функций и методов, которыми можно легко поделиться и использовать. В этой статье мы сосредоточимся на библиотеках C, но большинство современных языков используют библиотеки.

Полезность библиотек

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

Как мы можем воспользоваться этим в C?

Хороший вопрос. Если вы не знакомы с процессом компиляции на C с использованием gcc, пожалуйста, прочитайте мою предыдущую статью. В этой статье я буду говорить о разделяемых/динамических библиотеках, хотя есть и другой тип библиотек, называемых статическими библиотеками (о которых вы можете прочитать здесь).

Статические и динамические библиотеки

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

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

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

Последнее существенное различие между статическими и разделяемыми библиотеками — совместимость библиотек/изменения кода. Если вы хотите обновить функцию со статической библиотекой, каждую программу, использующую эту библиотеку, необходимо перекомпилировать, чтобы отразить эти изменения. Преимущество этого, однако, заключается в том, что вы можете быть уверены, что библиотека совместима с программой, использующей статическую библиотеку, поскольку именно с ней она была скомпилирована. Что касается разделяемых библиотек, их намного проще обновлять, так как не требуется перекомпиляция программ, которые их используют. В свою очередь, библиотека может быть обновлена ​​таким образом, что сделает ее несовместимой с некоторыми программами, что фактически сделает программы бесполезными.

Создание динамической библиотеки с помощью GCC

Допустим, вы решили, что динамическая библиотека лучше всего подходит для вашего приложения. Как мы делаем один? На самом деле это относительно просто, особенно по сравнению со статическими библиотеками.

Во-первых, нам нужно собрать все файлы, содержащие функции, которые мы хотим сделать доступными в нашей библиотеке. У вас может быть одна функция в файле (как показано на рисунке), все ваши функции в одном файле или где-то посередине. Вам также понадобится заголовок с прототипами всех ваших функций.

Теперь, когда мы все это организовали, нам нужна только одна «простая» команда: gcc -shared -fPIC -o liblibrary.so *.c

Подождите секунду... Что все это значит? Давайте сломаем это.

Опция -shared говорит сама за себя. Он сообщает gcc, что мы компилируем общую библиотеку и должны обрабатывать следующие параметры как таковые.

Затем параметр -fPIC указывает gcc компилировать с относительными адресами машинного кода, а не с абсолютными ссылками. Это означает, что код можно вставить куда угодно и сборка все равно будет работать, даже если «номера строк» ​​изменились. Это очень важно для разделяемой библиотеки, поскольку мы никогда не знаем, как может выглядеть код, использующий ее.

После этого опция -o liblibrary.so сообщает gcc, что наш выходной файл должен называться liblibrary.so. Это имя также следует соглашению для разделяемых библиотек, которое должно начинаться с lib, за которым следует имя библиотеки (в нашем случае library) и заканчиваться на .so.

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

Если хотите, вы можете увидеть, какие функции включены в вашу библиотеку, используя: nm -D --defined-only liblibrary.so.

Компиляция программы с GCC и общей библиотекой

Чтобы воспользоваться этой динамической библиотекой, мы должны указать, что мы используем ее, когда мы компилируем нашу программу. Давайте рассмотрим команду gcc program.c -L . -llibrary -o executable.

Мы говорим gcc скомпилировать program.c, довольно просто. Затем мы используем флаг -L, чтобы указать, что мы должны искать библиотеки в каталоге . (текущий каталог) (однако это происходит только во время компоновки). Далее мы говорим, что хотим использовать библиотеку (-l) и имя библиотеки library, поэтому в итоге получаем: -llibrary. После этого мы используем -o, чтобы назвать наш вывод executable. Прохладный! Давайте запустим!

О-о! Не может найти нашу библиотеку! Осталось сделать еще одну вещь, прежде чем мы сможем запустить программу, использующую разделяемую библиотеку. Нам нужно указать, где мы можем найти общие библиотеки. Для этого мы используем переменную среды $LD_LIBRARY_PATH. Это очень похоже на $PATH тем, что представляет собой список каталогов. Чтобы добавить каталог, в котором находится наша библиотека, мы можем использовать команду export. Например, я бы запустил:

export LD_LIBRARY_PATH=/home/vagrant/dl_backup:$LD_LIBRARY_PATH

:$LD_LIBRARY_PATH в конце означает, что если переменная $LD_LIBRARY_PATH уже имеет значение, оно будет добавлено в конец нашего нового пути, чтобы мы не потеряли пути.

Теперь попробуем снова запустить нашу программу:

Потрясающий! Но прежде чем вы уйдете, обратите внимание, что перекомпиляция не потребовалась, все, что нам нужно было сделать, это добавить его в $LD_LIBRARY_PATH, и это сработало! Принцип одинаков для любых изменений, внесенных в саму библиотеку, перекомпиляции не требуется.

Считаете ли вы эту статью полезной?

Пожалуйста, дай мне знать! Я хотел бы услышать, как я могу улучшить и что я сделал хорошо. Вы можете следить за мной в твиттере @TheBenKeener.