春招结束,发现面试的后面几面,会闻到c++的abi问题,大致问题就是,gcc编译的库,clang能用吗,或者在升级so文件的时候要注意什么,当时不知道,现在来总结一下。
这个abi问题,网上资料不是很多,有一些零零散散的,最后自己总结了一下以后,在这里做个笔记。
一般的软件为了模块分割,思路都是这样的:
模块写在so/dll文件中,使用exe加载并执行功能,更新只用更新dll、so就可以了。不用重新编译exe
这是一篇ABI兼容的文章/p/895451c7b678
ABI兼容的目的就是为了保证改变了dll,so以后,不用重新编译exe就可以直接使用。
当ABI不兼容,或者ABI出错的时候,会发生什么呢?我们来看一个例子
先给出一个继承图:
vclass -> obj
//interface.h#include<string>class vclass{public:std::string name;virtual std::string get_name()=0;};
//lib.h,继承自interface,重写虚函数#include"interface.h"#include<string>class obj : public vclass{public:std::string get_name();//获得nameobj(){name="子类";}};extern "C" vclass* get_obj(){ //使用C格式的函数命名return new obj;}
//lib的实现#include"lib.h"std::string obj::get_name(){return name;}
很简单的例子吧,就是实现了一个接口,然后返回name的值,随后我们在main中调用dlopen,来打开这个动态链接库:
#include<iostream>#include<memory>#include<dlfcn.h>#include"interface.h"using namespace std;int main(){void *handle=dlopen("./lib.so",RTLD_NOW);//加载so文件using func= vclass*(*)(void);func get_obj;get_obj=(func)dlsym(handle,"get_obj");//从so中获取函数vclass *obj1=(get_obj()); //获取一个对象cout<<obj1->get_name()<<endl; //能够成功得到子类的对象return 0;}
运行的结果很显而易见,直接会输出动态库中的"子类“
#makefilelib:lib.h lib.cpp g++ -g -fPIC -shared -o lib.so lib.cppmain:main.cppg++ -g main.cpp -o main -ldl
ok,现在问题来了,现在有另外一个同事,由于某种需求,在父类加了一个虚函数xxxx(),并且在get_name()虚函数前(这个顺序非常重要,因为虚函数表):
#include<string>class vclass{public:std::string name;virtual std::string xxxx(){return "哈哈哈哈";}virtual std::string get_name()=0;};
那么现在保持子类不动,main也不编译,直接重新编译lib
#makefilelib:lib.h lib.cpp g++ -g -fPIC -shared -o lib.so lib.cpp
现在运行main,你就会发现,我的get_name()呢?去哪了?怎么变成哈哈哈哈了?
这就是ABI的一个特点。
因为加入了新的一个虚函数,使得原来虚函数表的顺序由:
get_name;
变成了:
xxxx
get_name
但是main.cpp 中的obj->get_name(),在汇编中只是访问虚函数表中特定偏移量的函数,在这个例子中,访问的是虚函数表中第一个函数。那么我们通过修改虚函数表的大小,就会导致函数运行出错。所以,一旦涉及到需要动态运行so的,就要考虑到abi了。业界比较常用的,保持C++ ABI兼容的方法是Q指针和D指针,这个我们其他文章再慢慢道来。
如果觉得《C++中ABI的问题——1.ABI问题的产生》对你有帮助,请点赞、收藏,并留下你的观点哦!