msvc link.exe 有什么不同

@vrqq  August 20, 2021

起初是因为这段代码

//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,但生成可执行文件必须都所有符号都要有定义

原理分析

站在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


添加新评论