Linux共有ライブラリの最小実行可能APIとABIの例
この答えは、他の私の答えから抽出されました:アプリケーションバイナリインターフェイス(ABI)とは何ですか?でも、これも直接答えられるし、質問は重複していないと感じました。
共有ライブラリのコンテキストでは、「安定したABIを持っている」ことの最も重要な意味は、ライブラリの変更後にプログラムを再コンパイルする必要がないことです。
以下の例でわかるように、APIが変更されていなくても、ABIを変更してプログラムを壊すことが可能です。
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
called に追加するとし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
before を追加するとold_field
、ABIだけが破壊され、APIは破壊されないことに注意してください。
これが意味することは、もし私たちが main.c
、ライブラリに対してプログラムを、関係なく機能したということです。
また、たとえば関数のシグネチャを変更した場合、APIも壊れていました。
mylib_mystruct* mylib_init(int old_field, int new_field);
その場合、 main.c
コンパイルは完全に停止します。
セマンティックAPIとプログラミングAPIとABI
APIの変更を3番目のタイプ、つまりセマンティックの変更に分類することもできます。
たとえば、変更した場合
myobject->old_field = old_field;
に:
myobject->old_field = old_field + 1;
その後、これはAPIもABIも壊れませんでしたmain.c
が、それでも壊れます!
これは、プログラムで認識できる側面ではなく、関数が実行するはずの「人間による説明」を変更したためです。
ソフトウェアの正式な検証は、ある意味で「セマンティックAPI」から「プログラムで検証可能なAPI」へと移行するという哲学的な洞察を得たところです。
セマンティックAPIとプログラミングAPI
APIの変更を3番目のタイプ、つまりセマンティックの変更に分類することもできます。
セマンティックAPIは通常、APIが実行するはずの自然言語による説明であり、通常はAPIドキュメントに含まれています。
したがって、プログラムのビルド自体を壊すことなく、セマンティックAPIを壊すことが可能です。
たとえば、変更した場合
myobject->old_field = old_field;
に:
myobject->old_field = old_field + 1;
すると、プログラミングAPIもABIも壊れませんでしたが、 main.c
、セマンティックAPIは破壊しました。
プログラムでコントラクトAPIをチェックする方法は2つあります。
- 一連のコーナーケースをテストします。簡単ですが、いつでも見逃してしまうかもしれません。
- 正式な確認。実行は困難ですが、正確さの数学的証明を生成し、本質的にドキュメントとテストを「人間の」/マシンで検証可能な方法に統合します!もちろん、正式な説明にバグがない限り、;-)
Ubuntu 18.10、GCC 8.2.0でテスト済み。