链接、装载和库看完这个系列就够了(一)(静态库链接顺序问题)

初衷

工作中经常会碰到不同平台之间移植代码,项目中引入各种开源库,经常会碰到很多奇怪的编译、链接问题,这里做一下整理,尽量包含常见的大部分问题。首先我们来关注一下链接顺序引发的问题。

静态库链接顺序问题

测试代码

最常见的问题

正常情况下(每个静态库之间没有相互依赖关系),静态库之间链接顺序是不需要关注的,但是如果两个库之间存在相互的调用,就会出现链接问题,看一个例子(头文件只声明函数,不再贴出):

//lib_a1.c
#include "lib_a1.h"
int a1_fun()
{
        return 0;
}
//lib_a2.c
#include "lib_a2.h"
#include "lib_a1.h"
int a2_fun()
{
        a1_fun();
        return 0;
}
//main.c
#include "lib_a1.h"
#include "lib_a2.h"

int main()
{
        //a1_fun();
        a2_fun();
        return 0;
}
#Makefile
all:main
lib_a1.a: lib_a1.o
        ar rcs $@ $<
lib_a2.a: lib_a2.o
        ar rcs $@ $<

main: main.o lib_a1.a lib_a2.a
        gcc -o $@ main.o -l_a1 -l_a2 -L. 

clean:
        rm -rf *.o *.a *.so main

此时执行,会报未定义的问题(undefined reference to `a1_fun’):

#make clean;make
rm -rf *.o *.a *.so main
cc    -c -o main.o main.c
cc    -c -o lib_a1.o lib_a1.c
ar rcs lib_a1.a lib_a1.o
cc    -c -o lib_a2.o lib_a2.c
ar rcs lib_a2.a lib_a2.o
gcc -o main main.o -l_a1 -l_a2 -L.
./lib_a2.a(lib_a2.o): In function `a2_fun':
lib_a2.c:(.text+0xa): undefined reference to `a1_fun'
collect2: error: ld returned 1 exit status
Makefile:9: recipe for target 'main' failed
make: *** [main] Error 1

如果在Makefile中修改一下链接顺序,也就是先链接lib_a2.a,此时编译正常:

# make
gcc -o main main.o -l_a2 -l_a1 -L.

出现这样的问题的原因是静态链接库在链接的时候是有顺序的,具体来说,gcc在链接时是按照链接参数给定的顺序依次读入,如果首先读入的是lib_a2.a,此时发现a1_fun()函数还未定义,会加入未定义符号表,然后读入lib_a1.a时,发现a1_fun()在这里面定义,此时把a1_fun()从未定义的符号表中去除。链接完成后检查未定义表,此时没有未定义的符号,则链接正常。
反过来,如果一开始读取lib_a1.a,发现a1_fun()函数并没有在未定义符号表中,此时链接器不会把a1_fun()函数链接到最终的可执行文件中,然后后续读入lib_a2.a的时候,发现a1_fun()没有定义(因为上一步a1_fun()并没有链接进来),链接结束后就会报a1_fun()没有定义。

一个不好的解决办法

对于这种小问题,一个解决办法就是上面的判断每个静态库的依赖关系,按照依赖关系进行决定链接顺序。同时还有另外一个办法就是多次添加链接:

# make
gcc -o main main.o -l_a1 -l_a2 -l_a1 -L.

链接顺序是a1,a2,然后再链接a1,此时也可以链接正常,原因可以参考上面的解释。

使用-Xlinker

一旦项目很大的时候,或者静态库依赖复杂的时候,找出每个依赖关系并不简单,此时可以使用-Xlinker参数:

# make
gcc -o main main.o -Xlinker "-(" -l_a1 -l_a2 -Xlinker "-)" -L.

此时链接正常,这是因为链接器在处理”-(”和”-)”之间的静态库时,是会重复查找这些静态库的,不过这个参数带来的一个问题就是会导致链接速度变慢。是否使用-Xlinker需要自己去做权衡。

main.c带来的问题

好了,我们把Makefile恢复到初始状态:

#Makefile 
all:main

lib_a1.a: lib_a1.o
        ar rcs $@ $<
lib_a2.a: lib_a2.o
        ar rcs $@ $<

main: main.o lib_a1.a lib_a2.a
        gcc -o $@ main.o -l_a1 -l_a2 -L.

clean:
        rm -rf *.o *.a *.so main

之前把main.c的内容忽略了,其实main.c的内容也是对链接顺序有影响的。
如果把main.c的注释的那一行(//a1_fun())恢复,或者把main函数的两行调用都注释掉,那么链接又正常了。

# make clean;make
cc    -c -o main.o main.c
cc    -c -o lib_a1.o lib_a1.c
ar rcs lib_a1.a lib_a1.o
cc    -c -o lib_a2.o lib_a2.c
ar rcs lib_a2.a lib_a2.o
gcc -o main main.o -l_a1 -l_a2 -L.

其实原理是一样的,我们分析一下注释恢复这种情况,链接器首先读入main.o,发现a1_fun()和a2_fun()都没有定义,所以把它们加入未定义符号表,,然后链接lib_a1时,会把a1_fun()链接到可执行文件,同理链接lib_a2时,也类似。所以最终没有未定义的符号,所以就不会报错。这就引发一个问题奇怪的问题,如果我们把main.o写到最后

# make
gcc -o main -l_a1 -l_a2 -L. main.o
main.o: In function `main':
main.c:(.text+0xa): undefined reference to `a1_fun'
main.c:(.text+0x14): undefined reference to `a2_fun'
collect2: error: ld returned 1 exit status
Makefile:9: recipe for target 'main' failed
make: *** [main] Error 1

我们发现链接又报错了,原因和上面类似,不在重复。所以一个看似很简单的问题,其实隐藏着很多问题,所以也就能解释为什么我们在项目的处理中会碰到各种各样的奇怪的问题。在这里推荐一本书<<程序员的自我修养—链接、装载与库>>,这本书对链接进行了很深入的讲解。

发布了8 篇原创文章 · 获赞 0 · 访问量 232

猜你喜欢

转载自blog.csdn.net/zhensansan/article/details/104571731