Beispiel für eine minimal ausführbare API der gemeinsam genutzten Linux-Bibliothek im Vergleich zum ABI-Beispiel
Diese Antwort wurde aus meiner anderen Antwort extrahiert: Was ist eine Application Binary Interface (ABI)?aber ich hatte das Gefühl, dass es auch dieses direkt beantwortet und dass die Fragen keine Duplikate sind.
Im Zusammenhang mit gemeinsam genutzten Bibliotheken besteht die wichtigste Auswirkung eines "stabilen ABI" darin, dass Sie Ihre Programme nach dem Ändern der Bibliothek nicht neu kompilieren müssen.
Wie wir im folgenden Beispiel sehen werden, ist es möglich, den ABI zu ändern und Programme zu unterbrechen, obwohl die API unverändert bleibt.
Haupt 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
Kompiliert und läuft gut mit:
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
Nun sei angenommen , dass für v2 der Bibliothek, wir ein neues Feld hinzufügen möchten mylib_mystrict
genanntnew_field
.
Wenn wir das Feld zuvor old_field
wie folgt hinzugefügt haben :
typedef struct {
int new_field;
int old_field;
} mylib_mystruct;
und die Bibliothek wieder aufgebaut, aber nicht main.out
, dann schlägt die Behauptung fehl!
Dies liegt daran, dass die Zeile:
myobject->old_field == 1
hatte eine Assembly generiert, die versucht, auf die allererste int
Struktur zuzugreifen , die jetzt new_field
anstelle der erwarteten istold_field
.
Daher hat diese Änderung den ABI gebrochen.
Wenn wir jedoch new_field
nachher hinzufügen old_field
:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
Dann greift die alte generierte Assembly immer noch auf die erste int
Struktur zu, und das Programm funktioniert weiterhin, da wir den ABI stabil gehalten haben.
Hier ist ein vollautomatische Version dieses Beispiels auf GitHub .
Eine andere Möglichkeit, diesen ABI stabil zu halten, wäre die Behandlung mylib_mystruct
als undurchsichtige Struktur gewesen und nur über Methodenhelfer auf seine Felder zuzugreifen. Dies macht es einfacher, den ABI stabil zu halten, würde jedoch einen Leistungsaufwand verursachen, da wir mehr Funktionsaufrufe ausführen würden.
API gegen ABI
Im vorherigen Beispiel ist es interessant, dass die Zugabe new_field
vorold_field
, nur das ABI brach, aber nicht die API.
Was dies bedeutet, ist, dass wir unsere neu kompiliert hätten main.c
Programm gegen die Bibliothek hätten, es trotzdem funktioniert hätte.
Wir hätten die API jedoch auch beschädigt, wenn wir zum Beispiel die Funktionssignatur geändert hätten:
mylib_mystruct* mylib_init(int old_field, int new_field);
da in diesem Fall main.c
das Kompilieren ganz aufhören.
Semantische API gegen Programmier-API gegen ABI
Wir können API-Änderungen auch in einen dritten Typ einteilen: semantische Änderungen.
Zum Beispiel, wenn wir geändert hätten
myobject->old_field = old_field;
zu:
myobject->old_field = old_field + 1;
dann hätte dies weder API noch ABI kaputt gemacht, main.c
würde aber trotzdem kaputt gehen!
Dies liegt daran, dass wir die "menschliche Beschreibung" dessen, was die Funktion tun soll, geändert haben und nicht einen programmatisch wahrnehmbaren Aspekt.
Ich hatte gerade die philosophische Einsicht, dass die formale Verifizierung von Software in gewissem Sinne mehr von der "semantischen API" in eine "programmatisch überprüfbare API" verwandelt.
Semantische API vs Programmier-API
Wir können API-Änderungen auch in einen dritten Typ einteilen: semantische Änderungen.
Die semantische API ist normalerweise eine natürliche Beschreibung dessen, was die API tun soll, normalerweise in der API-Dokumentation enthalten.
Es ist daher möglich, die semantische API zu unterbrechen, ohne den Programmerstellung selbst zu unterbrechen.
Zum Beispiel, wenn wir geändert hätten
myobject->old_field = old_field;
zu:
myobject->old_field = old_field + 1;
dann hätte dies weder Programmier-API noch ABI kaputt gemacht, aber main.c
die semantische API würde beschädigt.
Es gibt zwei Möglichkeiten, die Vertrags-API programmgesteuert zu überprüfen:
- Testen Sie eine Reihe von Eckkoffern. Einfach zu machen, aber Sie könnten immer einen verpassen.
- formale Überprüfung . Schwieriger zu machen, aber einen mathematischen Korrektheitsnachweis zu erbringen, der Dokumentation und Tests im Wesentlichen auf eine "menschlich" / maschinenprüfbare Weise vereinheitlicht! Solange deine formale Beschreibung natürlich keinen Fehler enthält ;-)
Getestet in Ubuntu 18.10, GCC 8.2.0.