По мере развития проекта разработки программного обеспечения исходный код будет увеличиваться. Этот факт загрязняет make-файл и каталог, в котором находятся исходные файлы. Когда это происходит, лучше управлять проектом по-другому.
Решение этой проблемы заключается в объединении исходного кода в небольшие блоки связанных файлов. Часто некоторые строки кода необходимо выполнить в разных частях исходного кода. Чтобы избежать большого и неорганизованного программного кода с повторяющимися строками, используется метод разработки программного обеспечения, называемый код рефакторинг. Он заключается в изменении структуры исходного кода без ущерба для его функциональности. Применяя технику рефакторинга, в функции записываются повторяющиеся строки, которые вызываются в той части программного кода, которая должна быть выполнена. Чтобы вызывать эти функции в исходном коде, необходимо создать и использовать библиотеку.
Библиотека программ
Библиотека — это файл, в котором собраны несколько объектных файлов. Он включается после компиляции исходного кода как единое целое на фазе компоновки процесса компиляции. Библиотека позволяет программному коду быть:
- более модульный, что означает, что код можно разделить на модули, что делает его более читабельным и управляемым.
- быстрее перекомпилировать, потому что связывание исходного кода и объектных файлов, которые упорядочены в библиотеках, быстрее, чем связывание этого кода и объектных файлов по отдельности.
- легче обновлять, о чем свидетельствуют выше упомянутые причины.
Системы Unix позволяют создавать два типа библиотек: статические и разделяемые библиотеки. В первом объектные файлы устанавливаются в скомпилированный файл перед запуском программы. Во втором случае библиотека загружается в память системой, называемой динамическим загрузчиком, при запуске программы.
В этом посте объясняется, как работают статические библиотеки и как их создавать.
Как работают статические библиотеки?
Статические библиотеки хранят файлы, которые уже предварительно скомпилированы (двоичный код). Эти объектные файлы включаются в скомпилированный файл исходной программы на этапе компоновки. Затем создается исполняемый файл, содержащий двоичный код исходного файла вместе с объектными файлами, находящимися в статической библиотеке.
Этот рабочий процесс показан на рис. 1. В этом примере есть точка входа в файле main.c, которая вызывает функцию sort_char, хранящуюся в статической библиотеке. Исходный код преобразуется в объектный файл (main.o). Затем компоновщик добавляет объектный файл sort_char в скомпилированный файл и, наконец, генерирует исполняемый файл (main.out), который содержит оба кода.
С одной стороны, у такого рода библиотек есть некоторые преимущества:
- у него никогда не бывает проблем с совместимостью, поскольку код находится в одном исполняемом модуле.
- компоновка программы, чьи объектные файлы упорядочены в библиотеках, выполняется быстрее, чем компоновка программы, объектные файлы которой находятся на диске отдельно.
С другой стороны, статические библиотеки имеют некоторые недостатки. Размер исполняемого файла увеличивается, когда он связан со статической библиотекой, в которой хранится множество объектных файлов. Кроме того, если некоторые функции статической библиотеки вызываются в различных исходных программах, они встраиваются в каждый исполняемый файл, увеличивая занимаемый объем памяти. В этом случае лучше создать и использовать общую библиотеку.
Как их создать?
Предположим, что есть файл C (main.c), который вызывает функции rev_string и _strlen, находящиеся в статической библиотеке. Код файла main.c показан ниже:
#include <stdio.h> #include "header.h" /* main - Entry point. * Return: 0 - success. */ int main(void) { char string1[] = "dlrow olleH"; char string2 [] = "zyxwvutsrqponmlkjihgfedcba" rev_string(string1); rev_string(string2); printf("Size of string 1: %d\n",_strlen(string1)); printf("Size of string 2: %d\n",_strlen(string2)); return (0); }
rev_string — это функция, которая переворачивает массив символов:
#include "holberton.h" /** * rev_string - reverses a string. * @s: input string. * Return: no return. */ void rev_string(char *s) { int i, j; char temp; for (i = 0; *(s + count) != '\0'; i++) { for (j = i + 1; j > 0; j--) { temp = *(s + j); *(s + j) = *(s + (j - 1)); *(s + (j - 1)) = temp; } } }
_strlen — это функция, возвращающая длину строки:
#include "header.h" /** * _strlen - returns the length of a string. * @s: input string. * Return: length of a string. */ int _strlen(char *s) { int count = 0; while (*(s + count) != '\0') count++; return (count); }
Файл header.h содержит функции прототипа:
#ifndef _HEADER_H_ #define _HEADER_H_ int _strlen(char *s); void rev_string(char *s); #endif
Вывод исполняемого файла будет таким:
$ ./main Hello world abcdefghijklmnopqrstuvwxyz Size of string 1: 11 Size of string 2: 26 $
Предположим, что в текущем каталоге есть только описанные выше функции и заголовочный файл:
$ ls header.h rev_string.c _strlen.c $
Для создания статической библиотеки упомянутых выше функций необходимо сгенерировать их объектные файлы с помощью компилятора GCC:
$ gcc -Wall -pedantic -Werror -Wextra -c *.c $ ls header.h rev_string.c rev_string.o _strlen.c _strlen.o $
Флаг «-c» позволяет генерировать скомпилированные программы из файлов c, которые находятся в текущем каталоге (*.c), не связывая их.
Для создания статической библиотеки используется программа под названием «ar», что означает «архиватор». С помощью этой программы можно изменить и перечислить имена объектных файлов в статической библиотеке:
$ ar -rc lib_string.a rev_string.o _strlen.o $
При выполнении этой команды создается статическая библиотека с именем «lib_string.a», которая содержит копию объектного файла «rev_string.o» и «_strlen.o». Флаг «r» позволяет заменить старые объектные файлы новыми в библиотеке. Флаг «c» позволяет создать библиотеку, если она не существует.
С флагом «-t» перечислены имена объектных файлов:
$ ar-t
lib_string.a
rev_string.o _strlen.o $
Затем необходимо проиндексировать библиотеку. Индекс используется компилятором, особенно компоновщиком, для ускорения поиска символов внутри библиотеки. «ranlib» — это команда, используемая для индексации статической библиотеки:
$ ranlib lib_string.a $
Как их использовать?
Чтобы использовать библиотеку после ее создания, ее имя необходимо добавить при компиляции исходного кода:
$ gcc main.c -L. -l_string -o main $
Эта командная строка генерирует исполняемый файл. Флаг «-l» позволяет читать библиотеку через ее имя, предоставленное компоновщиком «_string». На этом этапе префикс «lib» и суффикс «.a» опускаются. Флаг «-L» указывает компоновщику, что библиотеки должны быть найдены в данном каталоге («.» для текущего каталога). Если этот флаг не используется, компилятор ищет библиотеку в каталоге usr/bin/lib, где находятся стандартные библиотеки.
При изменении исходного кода, как показано ниже, необходимо только запустить команду, которая генерирует исполняемый файл:
#include <stdio.h> #include "header.h" /* main - Entry point. * Return: 0 - success. */ int main(void) { char string1[] = "elif c.niam gnignahC"; rev_string(string1); printf("Size of string 1: %d\n",_strlen(string1)); return (0); }
Вывод будет:
$ gcc main.c -L. -l_string -o main $ ./main Changing main.c file Size of string 1: 20 $
Созданную статическую библиотеку можно использовать в другом проекте программирования. Предположим, что есть другой исходный код с именем test.c, содержащий следующий код:
#include <stdio.h> #include "header.h" /* main - Entry point. * Return: 0 - success. */ int main(void) { char str[] = "Using test.c file"; printf("Size of string 1: %d\n",_strlen(string1)); rev_string(str); return (0); }
Чтобы использовать библиотеку «_string» в этом c-файле, нужно всего лишь запустить команду, которая сгенерирует исполняемый файл:
$ gcc test.c -L. -l_string -o test $ ./test elif c.tset gnisU Size of string 1: 17 $