参考
YING’s 避免使用虚函数作为动态库的接口
陈硕’s C++ 工程实践(4):二进制兼容性
陈硕’s C++ 工程实践(5):避免使用虚函数作为库的接口
感悟总结
陈硕在博客的最后推荐使用impl手法对库进行接口的暴露,虽然我之前的库一般也是使用impl手法进行封装,但最初的目的只是为了将不必要的代码对外隐藏,比如说private的成员变量和方法。在拜读两位大神的博客后,对二进制兼容性这个概念有了从编译原理层面的了解,同时加深了程序在运行期与编译期对动态库与静态库的处理过程。
YING的博客以一个非常简单的DEMO证明:**以虚函数作为接口的动态库,若该库添加新的接口(旧接口不变),只通过替换动态库,而不用新的头文件对原APP的源码进行编译,会造成程序运行异常。**
以前我一直以为只要旧接口不变可以只替换动态库,看完上面的博客后,明白了两个概念,分别是bind-by-vtable-offset与bind-by-name。这两个概念在陈硕的博客最后的总结导出(主要是博主我基础知识薄弱),现摘抄如下
为什么 non-virtual 函数比 virtual 函数更健壮?因为 virtual function 是 bind-by-vtable-offset,而 non-virtual function 是 bind-by-name。加载器 (loader) 会在程序启动时做决议(resolution),通过 mangled name 把可执行文件和动态库链接到一起。
通过bind-by-vtable-offset与bind-by-name也同时导出了二进制兼容性这个话题。
application binary interface (ABI)
In computer software, an application binary interface (ABI) is an interface between two binary program modules; often, one of these modules is a library facility, and the other is a program that is being run by a user.
An ABI defines how data structures or computational routines are accessed in machine code, which is a low-level, hardware-dependent format;
in contrast, an API defines this access in source code, which is a relatively high-level, relatively hardware-independent, often human-readable format.
A common aspect of an ABI is the calling convention, which determines how data is provided as input to or read as output from computational routines;
在说ABI之前,我要先说说链接(link).
链接的本质就是把不同的object文件(目标文件)粘合到一个可执行文件,可以说有点像是搭积木或玩拼图。为了能使link成功,通常来说需要一套规则,而这个规则简单的说就是符号(Symbol)裁决,通常靠object文件中的Symbol Table来完成管理(这个规则是可以写一本书的量,我也不太懂)。那么什么是符号呢?在链接阶段,通常我们把函数和变量统称为符号(Symbol)。这是一个二元值,即(Symbol name, Symbol value),而这又分别对应到(函数名,函数地址)或者是(变量名,变量地址)。
在20世纪70年代以前,编译器编译源代码产生目标文件时,符号名与相对应的函数名或者变量名是一样的。但是随着时间的推移,越来越多的库出现,这样的话符号相同的情况就越来越多,也就是符号冲突日益严重。为了防止符号名冲突,Unix下的C就规定,C语言源代码文件中的所有全局变量和函数名经过编译后,相对应的符号名前面加上下划线“_”。这种简单而原始的处理方法还是能应付小规模的开发的,但是大规模的团队如果命名不规范仍然会出现问题。
所以,像C++这样后来设计的语言,增加了namespace的方法来解决某块之前的符号冲突。
C++除了namespace,还有override,overload,inherit,template….等等特性,这些都个符号管理增加了复杂度。C++的符号修饰的规则还和编译器相关,目前分成两个大派别,GCC和Virtual C++.有兴趣的请自己google.
所以,大家知道了C和C++相互调用时候为什么会出现extern “C”{……..}了吧?extern “C”{……..}告诉编译器采用C的符号规则,这样大家都统一了!
在说ABI之前仍然还要谈谈API.所谓API是指:Application Programming Interface。这是偏向源代码级别的接口,比如POSIX就是这样一个API标准。而ABI是指:Application Binary Interface。这是偏向二进制级别的接口,兼容程度比API更为严格。上面我们谈到的符号就是ABI中的一部分。除此之外还包含有C++对象的内存分布,函数调用方式,template如何实例化,异常的产生和捕获,内嵌函数的访问细节…..
本来大家都希望ABI能统一的,这样的话,移植就可以轻松很多,然后不幸的是:仅符号管理我们就可以看出GNU和Microsoft就有很大的不同,而且即使是Microsoft的不同版本的编译器也可能有不一样的行为。所以C++一直被人诟病的原因之一,就是ABI的兼容性不好。