前情概要
接入新库的时候遇到符号位定义问题。有人跟我说改一下库的顺序,就能解决问题。我试了下,嗯,问题是解决了。但是我总觉得库之间不应该有依赖关系,毕竟链接库就是个找符号的问题,如果有依赖,那a,b两个库,相互依赖,顺序怎么整呢?大型的软件,岂不是加一个库就要弄死人?带着这个疑问我做了一些小实验,来看一下一下库位置的顺序到底是如何影响符号未定义问题的。
问题根源
之所以链接顺序改变可以解决符号未定义问题,本质是因为以下两点导致的:
-
静态库是由.o文件拼出来的,链接静态库的时候,是以.o文件为单位进行的。
-
我们开发,编译机上,gcc/g++在进行链接的时候,使用的是从左到右,逐个文件处理,并且不走回头路的策略。
另外这个行为与编译器有关,在mac上用clang++测试,是没有这些问题的,顺序怎么打乱,都能链接上(确实没有符号定义的除外)。具体是上面哪个因素被打断,目前不确定,以后有需要的时候再研究吧。
我使用下面这个makefile和main.cpp,然后使用a,b,c,d四个cpp文件进行示例分析。注意,每个情况都是独立的a,b,c,d四个文件,不需要的时候你可以随意往文件里写个不相干的函数,不影响结果。不要弄混。
void hello();
int main(int argc, char *argv[])
{
hello();
return 0;
}
a.out: libab.a libcd.a
- g++ main.o libab.a libcd.a
- g++ main.o libcd.a libab.a
libab.a: a.o b.o
ar csr libab.a a.o b.o
libcd.a: c.o d.o
ar csr libcd.a c.o d.o
main.o: main.cpp
g++ -c main.cpp
a.o: a.cpp
g++ -c a.cpp
b.o: b.cpp
g++ -c b.cpp
c.o: c.cpp
g++ -c c.cpp
d.o: d.cpp
g++ -c d.cpp
简单说明 这个makefile。
分别编译abcd四个cpp文件,然后把ab两个文件链接成libab.a,把cd两个文件链接成libcd.a,然后用不同的顺序将这两个库跟main.o链接。
下面分情况讨论
情况 1
这种是最常见的。下面展示四个文件的内容,只需要关心a,b,c三个文件即可,d.cpp存在即可。
➜ seq1 git:(master) ✗ cat a.cpp
void hello_in_a()
{
}
➜ seq1 git:(master) ✗ cat b.cpp
void hello_in_b()
{
}
➜ seq1 git:(master) ✗ cat c.cpp
void hello_in_a();
void hello()
{
hello_in_a();
}
这个情况下,编译结果如下:
g++ main.o libab.a libcd.a
libcd.a(c.o): In function `hello()':
c.cpp:(.text+0x5): undefined reference to `hello_in_a()'
collect2: ld 返回 1
make: [a.out] 错误 1 (忽略)
g++ main.o libcd.a libab.a
也就是如果把ab放前面,cd放后面,就会有未定义符号。失败的那个case,链接器行为如下:
-
main.o,直接放进来,现在有一个未定义符号void hello()
-
查看libab.a的符号表,没有hello()这个符号,跳过这个库的所有.o文件
-
查看libbc.a的符号表,有hello()这个符号,把c.o放进来,现在引入一个新的未定义符号hello_in_a()
-
结束,发现未定义符号表还有符号,报undefined reference to `hello_in_a()'
成功的case,行为如下:
-
main.o,直接放进来,现在有一个未定义符号void hello()
-
查看libbc.a的符号表,有hello()这个符号,把c.o放进来,现在引入一个新的未定义符号hello_in_a()
-
查看libab.a的符号表,找到hello_in_a(),把a.o放进来
-
结束,未定义符号表为空,ok
情况2
这个情况相对少见,如果出现了还挺麻烦的。这里只关心ac两个cpp文件。内容如下:
➜ seq0 git:(master) ✗ cat a.cpp
void hello_unexist();
void hello()
{
hello_unexist();
}
➜ seq0 git:(master) ✗ cat c.cpp
#include <stdio.h>
void hello()
{
printf("hello");
}
运行结果如下:
libab.a(a.o): In function `hello()':
a.cpp:(.text+0x5): undefined reference to `hello_unexist()'
collect2: ld 返回 1
make: [a.out] 错误 1 (忽略)
g++ main.o libcd.a libab.a
可以看到,两个库分别定义了hello(),但是区别在于a.cpp的hello里面调用了一个不存在的hello_unexist方法。
失败的case链接器行为如下:
-
main.o,直接放进来,现在有一个未定义符号void hello()
-
查看libab.a的符号表,找到hello(),把a.o放进来,引入了未定义符号hello_unexist()
-
查看libcd.a的符号表,没有新的符号
-
结束,报错
成功的case行为如下:
-
main.o,直接放进来,现在有一个未定义符号void hello()
-
查看libcd.a的符号表,找到hello(),把c.o引进来。
-
查看libab.a,没有未定义符号
-
结束
情况3
这个也是比较少见的情况,ab相互依赖,并且他们的.o文件相互错过。使用abcd四个文件,内容如下:
➜ seq2 git:(master) ✗ cat a.cpp
void hello_in_d();
void hello_in_a()
{
hello_in_d();
}
➜ seq2 git:(master) ✗ cat b.cpp
void hello_in_d();
void hello_in_b()
{
hello_in_d();
}
➜ seq2 git:(master) ✗ cat c.cpp
void hello_in_b();
void hello()
{
hello_in_b();
}
➜ seq2 git:(master) ✗ cat d.cpp
void hello_in_b();
void hello_in_d()
{
hello_in_b();
}
编译结果如下:
libcd.a(c.o): In function `hello()':
c.cpp:(.text+0x5): undefined reference to `hello_in_b()'
collect2: ld 返回 1
make: [a.out] 错误 1 (忽略)
g++ main.o libcd.a libab.a
libab.a(b.o): In function `hello_in_b()':
b.cpp:(.text+0x5): undefined reference to `hello_in_d()'
collect2: ld 返回 1
make: [a.out] 错误 1 (忽略)
两个case,第一个,先ab的,链接器行为如下:
-
main.o,直接放进来,现在有一个未定义符号void hello()
-
查看libab.a的符号表,没找到hello(),什么都不放进来
-
查看libcd.a的符号表,有一个hello(),把c.o放进来,引入hello_in_b()未定义
-
结束,发现有未定义符号hello_in_b,报错
第二个,先cd的,链接器行为如下:
-
main.o,直接放进来,现在有一个未定义符号void hello()
-
查看libcd.a的符号表,有一个hello(),把c.o放进来,引入hello_in_b()未定义
-
查看libab.a的符号表,有一个hello_in_b(),把b.o放进来,引入hello_in_d()未定义
-
结束,此时有未定义符号hello_in_d,报错
这种情况,不管如何调整顺序,都是不能解决问题的。需要在后面重复一次libab.a或者libcd.a
上面的代码可以从https://github.com/johnzeng/linkage_sample.git上面clone到(不过seq目录的命名跟文章的情况罗列顺序不同就是了)
拓展问题:如果所有文件在一个静态库会怎么样
我尝试找一种办法,避免上面的问题。考虑了一下编译连接的原理,我将abcd四个cpp用不同顺序打包到一个静态库。看看会发生什么。修改makefile(只写了修改部分,其他不变):
a.out: libab.a libcd.a libabcd.a libdcba.a
- g++ main.o libab.a libcd.a
- g++ main.o libcd.a libab.a
- g++ main.o libabcd.a
- g++ main.o libdcba.a
libabcd.a: a.o b.o c.o
ar csr libabcd.a a.o b.o c.o d.o
libdcba.a: a.o b.o c.o
ar csr libdcba.a d.o c.o b.o a.o
在上面几个例子里,只有情况2(两个文件分别定义hello()的情况)有报错,并且是使用libabcd.a编译的时候报错:
g++ main.o libabcd.a
libabcd.a(a.o): In function `hello()':
a.cpp:(.text+0x5): undefined reference to `hello_unexist()'
根据上面的分析,我们可以进一步得出结论:
-
当所有obj文件在一个静态库的时候,不会出现因为打包顺序导致的符号未定义
-
当一个符号有两个定义的时候,根据ar生成静态库的时候输入的文件顺序,搜索符号。
本文探讨了在C++编译链接过程中,静态库顺序如何影响符号未定义问题。通过实验分析,揭示了静态库由.o文件组成,链接时按从左到右顺序处理,并不回溯的策略。同时,指出在Mac上使用clang++编译可能不会有此类问题。文章通过不同情况的示例,解释了符号未定义错误的原因,并提出将所有文件打包到一个静态库可以避免顺序问题。
1180

被折叠的 条评论
为什么被折叠?



