Linux shared library minimal runnable API vs contoh ABI
Jawaban ini telah diekstraksi dari jawaban saya yang lain: Apa itu antarmuka biner aplikasi (ABI)? tetapi saya merasa bahwa itu langsung menjawab yang satu ini juga, dan bahwa pertanyaan itu bukan duplikat.
Dalam konteks perpustakaan bersama, implikasi paling penting dari "memiliki ABI stabil" adalah bahwa Anda tidak perlu mengkompilasi ulang program Anda setelah perpustakaan berubah.
Seperti yang akan kita lihat dalam contoh di bawah ini, adalah mungkin untuk memodifikasi ABI, program yang rusak, meskipun API tidak berubah.
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
Kompilasi dan berjalan dengan baik dengan:
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
Sekarang, misalkan untuk v2 perpustakaan, kami ingin menambahkan bidang baru untuk mylib_mystrict
dipanggil new_field
.
Jika kami menambahkan bidang sebelumnya old_field
seperti pada:
typedef struct {
int new_field;
int old_field;
} mylib_mystruct;
dan membangun kembali perpustakaan tetapi tidak main.out
, maka pernyataan itu gagal!
Ini karena baris:
myobject->old_field == 1
telah menghasilkan perakitan yang mencoba mengakses yang pertama int
dari struct, yang sekarang new_field
bukan yang diharapkan old_field
.
Karenanya perubahan ini mematahkan ABI.
Namun, jika kami tambahkan new_field
setelah old_field
:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
kemudian perakitan lama yang dihasilkan masih mengakses yang pertama int
dari struct, dan program masih bekerja, karena kami menjaga ABI stabil.
Ini adalah versi yang sepenuhnya otomatis dari contoh ini di GitHub .
Cara lain untuk menjaga ABI ini stabil adalah memperlakukannya mylib_mystruct
sebagai struct buram , dan hanya mengakses ladangnya melalui metode helpers. Ini membuatnya lebih mudah untuk menjaga ABI stabil, tetapi akan menimbulkan overhead kinerja karena kami akan melakukan lebih banyak panggilan fungsi.
API vs ABI
Dalam contoh sebelumnya, menarik untuk dicatat bahwa menambahkan new_field
sebelumnya old_field
, hanya merusak ABI, tetapi bukan API.
Apa artinya ini, adalah bahwa jika kita telah mengkompilasi ulang main.c
program kita melawan perpustakaan, itu akan berhasil.
Namun kami juga telah merusak API jika kami mengubah misalnya tanda tangan fungsi:
mylib_mystruct* mylib_init(int old_field, int new_field);
karena dalam kasus itu, main.c
akan berhenti kompilasi sama sekali.
Semantic API vs programming API vs ABI
Kami juga dapat mengklasifikasikan perubahan API dalam tipe ketiga: perubahan semantik.
Sebagai contoh, jika kita telah memodifikasi
myobject->old_field = old_field;
untuk:
myobject->old_field = old_field + 1;
maka ini tidak akan merusak baik API maupun ABI, tetapi main.c
akan tetap rusak!
Ini karena kami mengubah "deskripsi manusia" dari apa fungsi yang seharusnya dilakukan daripada aspek yang terlihat secara program.
Saya hanya memiliki wawasan filosofis bahwa verifikasi formal perangkat lunak dalam arti memindahkan lebih dari "API semantik" ke lebih "API yang dapat diverifikasi secara terprogram".
API Semantik vs API Pemrograman
Kami juga dapat mengklasifikasikan perubahan API dalam tipe ketiga: perubahan semantik.
API semantik, biasanya merupakan deskripsi bahasa alami dari apa yang seharusnya dilakukan oleh API, biasanya termasuk dalam dokumentasi API.
Karena itu dimungkinkan untuk memecah API semantik tanpa merusak program itu sendiri.
Sebagai contoh, jika kita telah memodifikasi
myobject->old_field = old_field;
untuk:
myobject->old_field = old_field + 1;
maka ini tidak akan merusak pemrograman API, maupun ABI, tetapi main.c
API semantik akan rusak.
Ada dua cara untuk memeriksa API kontrak secara terprogram:
- menguji banyak kasus sudut. Mudah dilakukan, tetapi Anda mungkin selalu melewatkannya.
- verifikasi formal . Sulit dilakukan, tetapi menghasilkan bukti matematis tentang kebenaran, pada dasarnya menyatukan dokumentasi dan tes menjadi cara yang "dapat diverifikasi" oleh manusia / mesin! Selama tidak ada bug dalam deskripsi formal Anda tentu saja ;-)
Diuji di Ubuntu 18.10, GCC 8.2.0.