C语言的静态连接,简单的说就是将编译得到的目标文件.o(.obj),打包在一起,并修改目标文件中函数调用地址偏移量的过程。当在大一点的项目中,可能会遇到连接时,由于静态库在链接器命令行中出现顺序的问题,造成undefined reference错误。本文深入探讨一下这个问题,以及如何解决。
10年积累的成都网站设计、做网站、成都外贸网站建设公司经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站设计后付款的网站建设流程,更有长洲免费网站建设让你可以放心的选择与我们合作。
问题
如下图。假设有这么一个场景,在我们的构建系统中,构建了一个两个静态库文件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 -o a.out main.o liba.a libb.a
- ...undefined reference...
- error: ld returned 1 exit status
- # gcc -o a.out main.o libb.a liba.a
静态连接的算法
要理解上面这个问题,需要理解链接器在处理静态连接时候的算法。此处的阐述参考《深入理解计算机系统》中的“链接”章节。
首先,需要明确的是,链接器在考察库文件(.a)的时候,不是把库文件看做一个整体,而是将打包在其中的目标文件(.o)作为考察单元。在整个连接过程中,如果某个目标文件中的符号被用到了,那么这个目标文件会单独从库文件中提取出来,而不会把整个库文件连接进来。
然后,链接器在工作过程中,维护3个集合:需要参与连接的目标文件集合E、一个未解析符号集合U、一个在E中所有目标文件定义过的所有符号集合D。
以上面第一条命令gcc -o a.out main.o liba.a libb.a为例,我们来一步步看看链接器的工作过程:
当输入main.o后,由于main调用了a2.o和b1.o中的函数,而此时并没有在D中找到该符号,于是将引用的两个函数保存在U中,此处假设两个函数分别为a2_func和b1_func:
- E U D
- +---------------+---------------+---------------+
- | main.o | a2_func | |
- +---------------+---------------+---------------+
- | | b1_func | |
- +---------------+---------------+---------------+
接下来,输入liba.a,链接器发现,a2_func存在于liba.a的a2.o中,于是将a2.o加入到E,并在D中加入a2.o中所有定义的符号,其中包括a2_func,最后移除U中的a2_func,因为这个符号已经在a2.o中找到了的。然而,U中还有b1_func,所以连接还没有完成。
- E U D
- +---------------+---------------+---------------+
- | main.o | | a2_func |
- +---------------+---------------+---------------+
- | a2.o | b1_func | a2_func_other |
- +---------------+---------------+---------------+
接着,输入libb.a,同理,链接器发现b1_func定义在b1.o中,所以在E中加入b1.o,移除U中的b1_func,在D中加入b1.o里面所有定义的符号
- E U D
- +---------------+---------------+---------------+
- | main.o | | a2_func |
- +---------------+---------------+---------------+
- | a2.o | | a2_func_other |
- +---------------+---------------+---------------+
- | b1.o | | b1_func |
- +---------------+---------------+---------------+
然而,由于b1.o调用到a1.o中的函数,我们假设是a1_func,但在D中并没有找到这个函数,所以a1_func还需要加入到U中
- E U D
- +---------------+---------------+---------------+
- | main.o | | a2_func |
- +---------------+---------------+---------------+
- | a2.o | | a2_func_other |
- +---------------+---------------+---------------+
- | b1.o | a1_func | b1_func |
- +---------------+---------------+---------------+
但是,输入结束了!链接器发现U中还有未解析的符号,所以报错了!
可以看到由于链接器的算法实现,导致a1.o并没有被链接器考察,所以产生了未解析符号。仔细分析,可以知道,只要将liba.a和libb.a换一下顺序,就可以链接成功!
解决办法
一般来说有两种办法,一种是仔细分析依赖关系,并按照正确的顺序书写库文件的引用。原则是被依赖的尽量写在右边。但是在有些大型项目中,依赖关系可能并不容易梳理清楚。此时可以在命令行参数中重复对库文件的引用:
- # gcc -o a.out main.o liba.la libb.la liba.a
在上面的命令中,liba.a重复书写了两次。
如果你使用automake,可以用xxx_LIBADD和xxx_LDADD来控制目标文件的引用关系:
文章名称:C静态库连接的顺序问题
URL链接:http://www.shufengxianlan.com/qtweb/news36/267936.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联