最近在写一个C++静态库,就叫a.lib吧,a.lib编译一切正常,另一个程序b.exe使用a.lib,但在编译b.exe时,编译器报“error LNK2019: unresolved external symbol “”public CMyClass::AddField“””错误。因为之前没写过静态库程序,对于a.lib编译正常,但b.exe编译不通过,首先想到的是a.lib是不是要向动态库那样在定义CMyClass时前面加上__declspec(dllexport),但看了一下a.exe在调用CMyClass其它成员函数时却没有问题。这时定位到AddField的声明,按F12想跟到实现看看,编辑器却没有跳转,这时才恍然大悟,之前写AddField只写出了声明,没有去实现该函数,但是在a.lib内部也有其它地方调用了AddField,在编译时为何没问题?这就要涉及到C++的编译过程了。
C++程序的构建分为四个步骤:预编译、编译、汇编、链接。
每个步骤将文件编译成别的格式,如下:
步骤 | 未编译 | 预编译 | 编译 | 汇编 | 链接 |
文件 | tool.h、tool.cpp、test.cpp | tool.i、test.i | tool.s、test.s | tool.o、test.o | projectname.exe |
1.预编译:
预编译过程主要做4件事:
①展开头文件
②宏替换
③条件编译
④去掉注释
2.编译:
将代码转成汇编代码,并且在这个步骤中做了两件很重要的工作:
①编译器在每个文件中保存一个函数地址符表,该表中存储着当前文件内包含的各个函数的地址;
②因为这步要生成汇编代码,即一条条的指令,而调用函数的代码会被编译成一条call指令,call指令后面跟的是jmp指令的汇编代码地址,而jmp指令后面跟的才是“被调用的函数编译成汇编代码后的第一条指令”的地址,但是给call指令后面补充上地址的工作实在链接的时候才做的事情;
3.汇编:
将汇编代码转成机器码;
4.链接
编译器将前面产生的多个.o文件链接到一起生成一个.exe可执行文件;
但是在这个过程中,编译器做的一个重要的事情就是讲每个文件中call指令后面的地址补充上;方式是从当前文件的函数地址符表中开始找,如果没有,继续从别的文件的函数地址符表中找,找到后填补在call指令后面,如果找不到,则链接失败。
重温了C++的编译过程,我猜测a.lib编译并没有完全包含这四步,至少没有进行到链接这一步。对于静态库a.lib的使用,b.exe在链接阶段将自己生成的.o目标文件与a.lib进行链接,最终生成可执行文件b.exe。既然a.lib可与.o文件进行链接,那么静态库的格式必定与.o是兼容的。
继续查资料,看到这篇《静态库和动态库的区别》有这样一句话“静态库可以简单看成是一组目标文件(.o/.obj文件的集合),即很多目标文件经过压缩打包后形成的一个文件,类似delphi的.dcp文件”。这样就很明了了,静态库在编译时只进行到汇编这一步,并不进行链接,自然也不会检查某个被调用的函数是否有具体的实现了。