春招结束,发现面试的后面几面,会闻到c++的abi问题,大致问题就是,gcc编译的库,clang能用吗,或者在升级so文件的时候要注意什么,当时不知道,现在来总结一下。
这个abi问题,网上资料不是很多,有一些零零散散的,最后自己总结了一下以后,在这里做个笔记。
一般的软件为了模块分割,思路都是这样的:
模块写在so/dll文件中,使用exe加载并执行功能,更新只用更新dll、so就可以了。不用重新编译exe
这是一篇ABI兼容的文章
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();//获得name
obj(){
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;
}
运行的结果很显而易见,直接会输出动态库中的"子类“
#makefile
lib:lib.h lib.cpp
g++ -g -fPIC -shared -o lib.so lib.cpp
main:main.cpp
g++ -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
#makefile
lib: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指针,这个我们其他文章再慢慢道来。
因篇幅问题不能全部显示,请点此查看更多更全内容