It
分类: C/C++
2015-07-22 16:38:33
有一个同事C,在一个原来的C++动态库的基础上,为一个现场添加一个新的功能,需要在原来的class接口提供一个新的虚函数,以实现该功能。功能很简单,很快开发完毕并提交代码到主分支,该现场运行也没问题,该需求顺利关闭。当QA把该最新的库发布到其他现场,嗯哼,问题出现了。QA在发布该最新库时,问同事C,是否可以发布该最新库到其他现场。同事C想,我的修改是添加一个功能,并没有修改其他功能和接口,其他现场还是用原来的功能,肯定没有问题,就同意了发布最新库,这样可以在一个分支上进行维护。
那么,为什么一个现场没有问题,而另一个现场有问题呢?
这就是动态库的兼容性(ABI)问题。
没有出现问题的现场,是因为该现场为使用最新的功能,必然会重新编译链接使用该库的程序,所以不会出现问题;出现问题的现场,是因为该现场没有新的需求增加,使用该库的程序没有重新编译链接,导致使用该最新的库出现问题。
如果使用动态库,那么动态库发生变化,都需要重新编译链接一遍吗?如果答案为真,那么使用动态库优势少了一半,使用动态库仅仅是节约一点内存,在内存价格低廉的今天,好像该优势不大。其实不然,如果我们能够编写二进制兼容的库,我们可以不用重新编译链接一遍。
使用C++语言做开发,有许多'坑'需要我们时刻注意,ABI就是一个'坑'.
C++为什么会出现ABI问题呢?
看例子:
点击(此处)折叠或打开
知道了二进制兼容性后,应该如何避免问题?或者对动态库代码的修改,什么的修改会保持二进制兼容,什么样的修改会破坏二进制兼容呢?
1. 增加/减少虚函数
修改虚函数表内的排列顺序,即使把新增加的虚函数放到最后一个,也可能会引起问题,如该类作为父类被其他类继承等;
2. 修改函数的参数列表,无论是全局函数,类成员函数等,没有加extern "C"
由于C++支持同名函数重载,C++编译时,会对函数名字进行name mangling,如果修改了函数的参数列表,经过C++编译器编译后,函数的名称就变了;
3. 虚函数从有变无,或者从无改为有
改变该类的对象的大小
4. 增加减少成员变量,
这会改变该类的对象的大小,当在使用该库的程序中有如下代码:
pfoo = new Foo;
由于sizeof(Foo)发生了变化,分配的内存可能不够。另外当使用pfo->member_variable访问成员变量时,也可能会出错;当使用 inline setxxx(x)时,也可能会出错,因为inline函数可能已经编译进使用该库的程序代码中。
5. 应该还有,目前没有想到。
有那么多可能破坏二进制兼容性,我们是不是不要使用动态库了呢?其实这要看具体情况.
无论是动态库还是静态库,其根本目的是复用已有的、经过测试的成熟代码。我们看一下,静态库和动态库的优缺点.
优点:
使用静态库,主程序会把该库编译链接进程序本身,以后就与该库没有关系了,部署简单。
缺点:
1. 如果一台机器上有多个程序使用该库,会浪费一部分空间资源。
2. 修改了库,使用该库的程序必须重新编译连接。
优点:
1. 介意一部分空间资源,如果一台机器上只有一个程序使用该库,也谈不上节约空间资源了。
2. 动态库修改后,是二进制兼容的,可以不用重新编译。这样当发布升级新的功能时,只更新部分动态库。
缺点:
1. 部署相对复杂;
2. 要注意二进制的兼容性。
如果使用静态库,就不会有二进制兼容性了。如果可以,使用静态库吧,呵呵。
如果必须使用动态库,那么最好做到如下几点:
1. 尽可能的不要使用虚函数作为接口;
2. 增加一个间接层,使用pimpl。如下:
点击(此处)折叠或打开