Пример использования совместно используемой библиотеки Linux с минимальным объемом запуска
В контексте разделяемых библиотек наиболее важным следствием «наличия стабильного ABI» является то, что вам не нужно перекомпилировать свои программы после изменения библиотеки.
Так, например:
если вы продаете разделяемую библиотеку, вы избавляете своих пользователей от необходимости перекомпилировать все, что зависит от вашей библиотеки, для каждого нового выпуска
если вы продаете программу с закрытым исходным кодом, которая зависит от общей библиотеки, присутствующей в дистрибутиве пользователя, вы можете выпустить и протестировать меньше предварительных сборок, если уверены, что ABI стабилен в определенных версиях целевой ОС.
Это особенно важно в случае стандартной библиотеки C, на которую ссылаются многие программы в вашей системе.
Теперь я хочу привести минимальный конкретный пример этого.
main.c
#include <assert.h>
#include <stdlib.h>
#include "mylib.h"
int main(void) {
mylib_mystruct *myobject = mylib_init(1);
assert(myobject->old_field == 1);
free(myobject);
return EXIT_SUCCESS;
}
mylib.c
#include <stdlib.h>
#include "mylib.h"
mylib_mystruct* mylib_init(int old_field) {
mylib_mystruct *myobject;
myobject = malloc(sizeof(mylib_mystruct));
myobject->old_field = old_field;
return myobject;
}
mylib.h
#ifndef MYLIB_H
#define MYLIB_H
typedef struct {
int old_field;
} mylib_mystruct;
mylib_mystruct* mylib_init(int old_field);
#endif
Компилируется и работает нормально с:
cc='gcc -pedantic-errors -std=c89 -Wall -Wextra'
$cc -fPIC -c -o mylib.o mylib.c
$cc -L . -shared -o libmylib.so mylib.o
$cc -L . -o main.out main.c -lmylib
LD_LIBRARY_PATH=. ./main.out
Теперь предположим, что для v2 библиотеки мы хотим добавить в mylib_mystruct
новое поле с именем new_field
.
Если мы добавили поле до old_field
как в:
typedef struct {
int new_field;
int old_field;
} mylib_mystruct;
и перестроить библиотеку, но не main.out
, тогда утверждение не выполняется!
Это потому, что строка:
myobject->old_field == 1
сгенерировал сборку, которая пытается получить доступ к самой первой int
структуры, которая теперь new_field
вместо ожидаемого old_field
.
Поэтому это изменение нарушило ABI.
Если, однако, мы добавим new_field
после old_field
:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
тогда старая сгенерированная сборка все еще обращается к первой int
структуры, и программа все еще работает, потому что мы сохранили ABI стабильным.
Вот полностью автоматизированная версия этого примера на GitHub .
Другим способом сохранения стабильности ABI было бы рассматривать mylib_mystruct
как непрозрачную структуру и обращаться к ее полям только через помощники методов. Это упростит поддержание стабильности ABI, но приведет к снижению производительности, поскольку мы будем выполнять больше вызовов функций.
API против ABI
В предыдущем примере интересно отметить, что добавление new_field
перед old_field
только нарушило ABI, но не API.
Это означает, что если бы мы перекомпилировали нашу main.c
программу с библиотекой, она бы работала независимо.
Однако мы бы также нарушили API, если бы изменили, например, сигнатуру функции:
mylib_mystruct* mylib_init(int old_field, int new_field);
, поскольку в этом случае main.c
вообще перестанет компилироваться.
Семантический API против API программирования
Мы также можем классифицировать изменения API по третьему типу: семантические изменения.
Семантический API, как правило, представляет собой описание на естественном языке того, что должен делать API, обычно включается в документацию API.
Поэтому возможно нарушить семантический API, не нарушая саму сборку программы.
Например, если мы изменили
myobject->old_field = old_field;
до:
myobject->old_field = old_field + 1;
тогда это не нарушило бы ни API программирования, ни ABI, но main.c
семантический API сломался бы.
Существует два способа программной проверки API контракта:
- проверить кучу угловых чехлов. Это легко сделать, но вы всегда можете пропустить один.
- формальная проверка . Сложнее сделать, но производит математическое доказательство правильности, по существу объединяя документацию и тесты в «человеческий» / машинно проверяемый способ! Пока, конечно, в вашем официальном описании нет ошибки; -)
Список всего, что нарушает ABI общей библиотеки C / C ++
TODO: найти / создать окончательный список:
Пример минимального запуска Java
Что такое двоичная совместимость в Java?
Протестировано в Ubuntu 18.10, GCC 8.2.0.