Приклад API з мінімальною експлуатацією для спільної бібліотеки Linux та ABI
Ця відповідь отримана з моєї іншої відповіді: Що таке бінарний інтерфейс програми (ABI)? але я відчував, що він безпосередньо відповідає і на це, і що питання не є дублюючими.
У контексті спільних бібліотек найважливішим наслідком "мати стабільний ABI" є те, що вам не потрібно перекомпілювати свої програми після зміни бібліотеки.
Як ми побачимо на прикладі нижче, можна змінити ABI, порушуючи програми, навіть якщо API не змінюється.
main.c
#include <assert.h>
#include <stdlib.h>
#include "mylib.h"
int main(void) {
mylib_mystrict *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_mystrict
call 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 .
Іншим способом збереження цього АБІ було б ставитися 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
взагалі перестане складати.
Semantic API vs API програмування проти ABI
Ми також можемо класифікувати зміни API на третій тип: семантичні зміни.
Наприклад, якби ми змінили
myobject->old_field = old_field;
до:
myobject->old_field = old_field + 1;
тоді це не порушило б ні API, ні ABI, але main.c
все-таки зламається!
Це тому, що ми змінили "людський опис" того, що функція повинна виконувати, а не програмно помітний аспект.
Я просто мав філософське розуміння того, що формальна перевірка програмного забезпечення в певному сенсі переходить більше від "семантичного API" до більш "програмно перевіреного API".
Семантичний API проти API програмування
Ми також можемо класифікувати зміни API на третій тип: семантичні зміни.
Семантичний API - це звичайний опис мови на природному мові того, що повинен робити API, як правило, включений у документацію API.
Тому можливо розбити семантичний API, не порушуючи побудову програми.
Наприклад, якби ми змінили
myobject->old_field = old_field;
до:
myobject->old_field = old_field + 1;
тоді це не порушило б ні API програмування, ні ABI, але main.c
семантичний API не зламався.
Існує два способи програмної перевірки API контракту:
- перевірити купу кутових справ. Це легко зробити, але ви завжди можете пропустити його.
- формальна перевірка . Це важче зробити, але виробляє математичний доказ коректності, по суті об'єднуючи документацію та тести на "людину" / машину, що може бути перевірена! Поки у вашому офіційному описі звичайно немає помилки ;-)
Випробувано в Ubuntu 18.10, GCC 8.2.0.