Linux共享库最小可运行API与ABI示例
在共享库的上下文中,“具有稳定的ABI”最重要的含义是,在更改库后,您无需重新编译程序。
如下面的示例所示,即使API不变,也可以修改ABI,破坏程序。
main.c
#include
#include
#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
#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添加一个名为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的稳定。
保持此ABI稳定的另一种方法是将mylib_mystruct视为opaque struct,并且只能通过方法助手来访问其字段。这样可以更轻松地保持ABI的稳定,但是会增加性能开销,因为我们需要执行更多的函数调用。
API与ABI
在前面的示例中,有趣的是注意到在new_field之前添加old_field只会破坏ABI,而不会破坏API。
这意味着,如果我们针对库重新编译main.c程序,则无论如何它都可以工作。
我们也会破坏API,但是如果我们更改了例如函数签名:
mylib_mystruct* mylib_init(int old_field, int new_field);
因为在这种情况下,main.c将完全停止编译。
语义API与编程API与ABI
我们还可以将API更改分类为第三种类型:语义更改。
例如,如果我们已经修改
myobject->old_field = old_field;
收件人:
myobject->old_field = old_field + 1;
这将不会破坏API或ABI,但是main.c仍然会破坏!
这是因为我们更改了该功能应该执行的“人工描述”,而不是程序上引人注目的方面。
我只是有一种哲学上的见解,即formal verification of software在某种意义上将更多的“语义API”转移到了一个“可程序验证的API”中。
语义API与编程API
我们还可以将API更改分类为第三种类型:语义更改。
语义API通常是API应该执行的自然语言描述,通常包含在API文档中。
因此可以在不破坏程序本身的情况下破坏语义API。
例如,如果我们已经修改
myobject->old_field = old_field;
收件人:
myobject->old_field = old_field + 1;
这将不会破坏编程API或ABI,但是main.c语义API会破坏。
有两种方法可以以编程方式检查合同API:
测试一些极端情况。容易做到,但您可能总是会错过一个。
formal verification。难度较大,但会产生正确性的数学证明,从本质上将文档和测试统一为“人工” /机器可验证的方式!只要您对课程的形式描述中没有错误;-)
在Ubuntu 18.10,GCC 8.2.0中进行了测试。