По мере развития проекта разработки программного обеспечения исходный код будет увеличиваться. Этот факт загрязняет 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
$