C语言的静态连接,简单的说就是将编译好的目标文件.o(.obj)打包在一起,修改目标文件中的函数调用地址偏移的过程。在较大的项目中连接时,由于静态库在链接器命令行中出现的顺序,可能会导致未定义的引用错误。本文深入探讨了该问题及其解决方法。问题如下图所示。假设有这样一个场景,在我们的构建系统中,构建了两个静态库文件liba.a和libb.a,其中liba.a包含了两个目标文件a1.o和a2.o,而libb.a包含了一个对象文件b1.o.希望main.o与liba.a和libb.a静态链接。注意黄色箭头表示调用关系:b1.o需要调用a1.o中的函数,main.o调用a2.o和b1.o中的函数。你可以把.o文件理解为对应的.c文件。那么下面两条命令,哪一条会执行成功呢?注意这两个命令唯一的区别是liba.a和libb.a#gcc-oa.outmain.oliba.alibb.a...undefinedreference的书写顺序不同。..error:ldreturned1exitstatus#gcc-oa.outmain.olibb.aliba.aStaticLinkAlgorithm要理解上面的问题,需要理解链接器在处理静态链接时的算法。这里的解释可以参考《深入理解计算机系统》中的“链接”章节。首先需要明确的是,链接器在检查库文件(.a)时,并不是把库文件作为一个整体来看待,而是以打包在其中的目标文件(.o)为检查单位。在整个链接过程中,如果使用了目标文件中的某个符号,则目标文件会单独从库文件中提取出来,不会将整个库文件链接进来。那么,链接器在工作过程中会维护三套:需要参与连接的目标文件集合E,未解析符号集合U,E中所有目标文件定义的所有符号集合D。以第一个命令gcc-oa.outmain.oliba.alibb.a为例,我们一步步看一下链接器的工作过程:输入main.o后,因为main调用了a2.o和b1.o中的函数,但是此时在D中找不到该符号,所以将引用的两个函数保存在U中,假设这两个函数是a2_func和b1_func:EUD+---------------+----------------+------------+|main.o|a2_func||+----------------+----------------+--------------+||b1_func||+----------------+----------------+---------------+接下来进入liba.a,链接器发现liba.a的a2.o中存在a2_func,于是将a2.o添加到E中,将a2.o中定义的所有符号添加到D中,包括a2_func,以及最后去掉U中的a2_func,因为在a2.o中已经找到了这个符号。但是U里面还有b1_func,所以连接还没有完成。EUD+----------------+----------------+----------------+|main.o||a2_func|+----------------+----------------+---------------+|a2.o|b1_func|a2_func_other|+----------------+----------------+-------------+接下来进入libb.a,同样,链接器发现b1_func定义在b1.o中,所以将b1.o添加到E中,将b1_func从U中移除,添加b1.o到D中的所有符号EUD+----------------+------------+----------+|main.o||a2_func|+--------------+--------------+---------------+|a2.o||a2_func_other|+----------------+--------------+----------------+|b1.o||b1_func|+----------------+-------------+----------------+但是,由于b1.o调用了a1.o中的函数,我们假设是a1_func,但是这个D中找不到function,所以需要在U中加入a1_funcEUD+----------------+---------------+----------------+|main.o||a2_func|+----------------+---------------+----------------+|a2.o||a2_func_other|+-------------+---------------+----------------+|b1.o|a1_func|b1_func|+--------------+----------------+--------------+但是,输入结束!Linker发现U中有未解析的符号,所以报错!可以看出,由于链接器的算法实现,链接器没有检查a1.o,所以产生了未解析的符号。仔细分析就可以知道,只要改变liba.a和libb.a的顺序,就可以链接成功!一般来说,有两种解决方法。一是仔细分析依赖关系,按照正确的顺序编写库文件。参考。原则是尽量写在右边。但是在一些大型项目中,依赖关系可能不太容易理清。这时候可以在命令行参数中重复引用库文件:#gcc-oa.outmain.oliba.lalibb.laliba.a上面的命令中,liba.a写了两次。如果使用automake,可以使用xxx_LIBADD和xxx_LDADD来控制目标文件的引用关系:xxx_LIBADD:对于目标文件是库文件或可执行文件,需要使用该选项。表示打包目标库文件时,会将依赖文件一起打包。xxx_LDADD:该选项可用于可执行文件控制链接器的参数。如果你能分析清楚依赖关系,就可以在这个选项中按照正确的顺序写入,才能成功连接。
