Linux共享库最小可运行API vs ABI示例
这个答案是从我的另一个答案中提取出来的:什么是应用程序二进制接口(ABI)?但我觉得它也直接回答了这个问题,而且这些问题不是重复的。
在共享库的上下文中,“拥有稳定的ABI”最重要的含义是,在库更改后不需要重新编译程序。
正如我们将在下面的示例中看到的,即使API没有改变,也可以修改ABI,从而破坏程序。
c
#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;
重建了图书馆,但不是主要的。Out,则断言失败!
这是因为这一行:
myobject->old_field == 1
已生成程序集,该程序集试图访问结构体的第一个int,该结构体现在是new_field,而不是预期的old_field。
因此,这个更改破坏了ABI。
但是,如果我们在old_field之后添加new_field:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
那么旧生成的程序集仍然访问结构的第一个int,程序仍然可以工作,因为我们保持了ABI的稳定。
下面是这个例子在GitHub上的一个全自动版本。
保持此ABI稳定的另一种方法是将mylib_mystruct视为不透明结构,仅通过方法帮助程序访问其字段。这样可以更容易地保持ABI的稳定,但是由于我们要进行更多的函数调用,因此会产生性能开销。
API 与 ABI
在前面的例子中,有趣的是,在old_field之前添加new_field只破坏了ABI,而没有破坏API。
这意味着,如果我们根据标准库重新编译main.c程序,无论如何它都会工作。
然而,如果我们改变了例如函数签名,我们也会破坏API:
mylib_mystruct* mylib_init(int old_field, int new_field);
因为在这种情况下,main.c将完全停止编译。
语义API vs编程API
我们还可以将API更改分为第三种类型:语义更改。
语义API通常是API应该做什么的自然语言描述,通常包含在API文档中。
因此,可以在不破坏程序构建本身的情况下破坏语义API。
例如,如果我们修改了
myobject->old_field = old_field;
to:
myobject->old_field = old_field + 1;
那么这既不会破坏编程API,也不会破坏ABI,但是main.c语义API会破坏。
有两种方法可以通过编程方式检查合约API:
测试一些极端情况。这很简单,但你可能总是错过一个。
正式的验证。更难做到,但产生了正确性的数学证明,本质上是将文档和测试统一为“人”/机器可验证的方式!当然,前提是你的正式描述中没有bug;-)
在Ubuntu 18.10, GCC 8.2.0中测试。