Linux 共享库最小可运行 API 与 ABI 示例
这个答案是从我的另一个答案中提取的:什么是应用程序二进制接口(ABI)?但我觉得它也直接回答了这个问题,而且问题不是重复的。
在共享库的上下文中,“拥有稳定的 ABI”最重要的含义是您不需要在库更改后重新编译程序。
正如我们将在下面的示例中看到的,即使 API 未更改,也可以修改 ABI,破坏程序。
主程序
#include <assert.h>
#include <stdlib.h>
#include "mylib.h"
int main(void) {
mylib_mystruct *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_mystruct
被调用添加一个新字段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
beforeold_field
只会破坏 ABI,但不会破坏 API。
这意味着,如果我们main.c
针对库重新编译我们的程序,无论如何它都会工作。
但是,如果我们更改了函数签名,我们也会破坏 API:
mylib_mystruct* mylib_init(int old_field, int new_field);
因为在这种情况下,main.c
将完全停止编译。
语义 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 中测试。