C/CPP 静态链接顺序

2019-01-28 linux c/cpp

静态链接和动态链接最大的区别就在于链接的时机不一样,静态链接是在生车可执行程序前,而动态链接的进行则是在程序执行时,而且静态链接库的位置不同将会影响二进制文件生成。

链接顺序

在使用静态库的时候,经常会遇到因为连接顺序不同导致出现 undefined reference 的报错,可以通过 nm libxx.a | grep xxx 命令查看是否存在对应的符号。注意,对于 C++ 来说,为了支持函数重载,编译器就引入了 name mangling 机制,原名就需要通过 c++filt 转换。

如果仍然存在,那么就可能是因为链接顺序导致的问题,如下是一个简单的示例。

----- 只进行编译,会分别生成add.o和main.o文件
# gcc -c add.c
# gcc -c main.c
----- 然后打包成一个静态库
# ar -rc math.a add.o

在生成最终二进制文件时就会涉及到了链接过程中的顺序问题。

----- 这里会报add未定义的错误
# gcc -o main math.a main.o
/usr/bin/ld: main.o: in function `main':
main.c:(.text+0xf): undefined reference to `add'
collect2: error: ld returned 1 exit status
----- 这里可以编译成功
# gcc -o main main.o math.a

也就是说,正常越基础的库越应该放在右边。

基本原理

静态库实际上包含了所有的编译后的 *.o 文件,链接器从左至右搜索的同时维护了一个 undefined 列表,一旦遇到没有定义的内容,就会将它加到列表中,如果搜索到了定义的内容,则抽取出进行链接,并将其从 undefined 中移除,为了减少目标的体积,其它对象文件会被删除。

这样就意味着,如果一个静态库在搜索过程中不需要,那么就会被丢弃,而后续一旦遇到了依赖的库就会报未定义的错误。

其它

链接参数

但是可以通过 --start-group--end-group 取消依赖顺序,会在该范围内循环查找未定义的符号,直到不再存在未定义,这样就可以规避掉依赖关系,但是会导致最终链接时间增加。

----- 添加链接选项同样可以编译成功
# gcc -o main -Wl,--start-group math.a main.o -Wl,--end-group

CMake

还有就是 CMake 会自动在链接过程中解决依赖,简单来说就是通过 target_link_libraries() 指定依赖以及参数是,其依赖库对应的依赖会添加到真正链接时的末尾,详细可以参考最终生成的 link.txt 文件,该文件可以直接修改。

所以,如果使用的 start-group 参数,同时有相关的依赖,那么就可能会导致莫名其妙的 undefined reference 报错。