Приклад 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_mystrictcall 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.