起初是因为这段代码
//A.cpp
CC::sum(int a, int b) { return a+b; }
//A.h
#ifdef _WIN32
#ifdef A_API_IMPLEMENT
#define A_API __declspec(dllexport)
#else
#define A_API __declspec(dllimport)
#endif
#else
#ifdef A_API_IMPLEMENT
#define A_API __attribute__((visibility("default")))
#else
#define A_API
#endif
#endif
class CC {
public:
A_API CC(int a) {}
virtual int sum(int p, int q);
};
//main.cpp
int main() {
CC *ptr = new CC(10);
ptr->sum(1, 2);
return 0;
}
出于一些dll隔离的目的,需要编译a.dll (liba.so) 和 main.exe
Windows上的表现
默认参数,编译,链接,一切正常
Linux上的表现(gcc,clang)
可以编译出a.so 但下一步提示
[vrqq@rhel test]$ g++ main.cpp -lliba.so -o mainexe
/tmp/ccHFtj3y.o: In function `CC::CC(int)':
main.cpp:(.text._ZN2CCC2Ei[_ZN2CCC5Ei]+0xc): undefined reference to `vtable for CC'
collect2: error: ld returned 1 exit status
咦,很奇怪不是?
仔细地看了很多
ABI、vtable什么的,后来发现原因直拍大腿
- Windows下link.exe 即使生成exe 也默认允许undefined function
- Linux下 ld / ld.lld 在生成.so时 允许undefined function,但生成可执行文件必须都所有符号都要有定义
原理分析
- Windows下使用dllimport / dllexport 标示导入表、导出表
- Linux下站在win角度上看可以理解成,只有导出表
- 无论windows下的a.dll,还是linux下的liba.so,对外的CC::sum(int,int)这个函数都没有暴露definition
注 what is definition: https://docs.microsoft.com/en-us/cpp/cpp/declarations-and-definitions-cpp?view=msvc-160
站在main.cpp角度上看,他是一个未定义函数,但为什么能运行呢?
- 在class中,所有virtual function看作一个整体: vtable。只要其中某个函数没定义,则表现为vtable未定义
- 任何一个人,只要拿到了.h 就知道了vtable的样子
- 然后
new CC
生成了一枚指针,所以站在main的角度上看,在运行过程中,就可以根据.h内vtable的定义,加上ABI约定,就可以得到ptr + x
这个地址,就是sum函数的地址,直接call ptr + x
即可在运行过程中访问该函数。
linker为什么会报错呢?
linker是要拿到函数的definition(implement)代码的offset in dll,所以link时候查全局表,找不到这个函数,当然就不知道指向哪里。
那linker看到virtual还去傻傻索要,他错了吗?
他没错,无论这个函数名称暴露或不暴露,都要有一段implement的代码,这段代码可以find by function name in build time,也可以find in vftable in runtime。
而linker看到这个int sum(int, int)
`不是pure virtual理所应当的认为他就应该有definition,而是否需要 “找到它” 还是 “悬空放置” 则取决于编译参数。(linker不管这段definition是否真的被使用)
如果是pure virtual 呢?class CC{ virtual int sum(int,int) = 0; }
当然就直接链接通过啦!
另外刚开始的那段代码,在ld链接时加上参数-Wl,--unresolved-symbols=ignore-all
,不让ld非得找到definition 也可以顺利编译了!
附录
Declarations vs definitions: https://docs.microsoft.com/en-us/cpp/cpp/declarations-and-definitions-cpp?view=msvc-160
msvc支持的dllexport和dllimport: https://docs.microsoft.com/en-us/cpp/cpp/using-dllimport-and-dllexport-in-cpp-classes?view=msvc-160
一个中文的不是太由浅入深的介绍: https://zhuanlan.zhihu.com/p/268324735
扩展阅读: msvc novtable用法 https://docs.microsoft.com/en-us/cpp/cpp/novtable?view=msvc-160