链接静态库后再链接共享库时链接器未移除未使用符号的原因及链接顺序对垃圾回收的影响
这是个非常典型的链接器行为问题,核心在于链接器处理静态库和动态库的顺序逻辑,以及-Wl,-gc-sections生效的前提条件。咱们一步步拆解原因:
1. 链接器处理输入文件的核心规则
链接器是按命令行顺序逐个扫描输入文件的,而且对静态库(.a)和动态库(.so)的处理逻辑完全不一样:
- 动态库:链接时不会把它的代码塞进可执行文件,只会记录“运行时要加载这个库”的依赖信息,以及把未定义符号的引用指向这个库。
- 静态库:链接器会挨个检查归档里的目标文件,如果某个目标文件能解决当前还没定义的符号,就把整个目标文件提出来加入链接流程;要是没有未定义符号需要它解决,就直接忽略这个静态库。
另外,哪怕你用了-ffunction-sections把每个函数拆成独立的section,链接器处理静态库时还是以目标文件为单位提取——也就是说,只要目标文件里有一个符号被需要,整个目标文件的所有section都会被加到可执行文件里(之后才会尝试用-Wl,-gc-sections清理)。
2. 第一种链接顺序:app.o libstatic.a libshared.so
咱们跟着链接流程走一遍:
app.o里有两个未定义的符号:use_shared(main调用的)和use_static(main直接调用的)。- 链接器处理
libstatic.a:发现static.o能提供use_static,就把整个static.o提出来加入链接。这时候use_static被定义了,但use_shared还没着落。 - 链接器处理
libshared.so:记录下要依赖这个库,并用它解决use_shared的未定义问题。现在所有符号都有定义了。 - 执行
-Wl,-gc-sections:这里的关键是,unused_in_static是默认的全局可见符号(你没加-fvisibility=hidden),链接器默认会觉得它可能被外部动态加载的模块引用,所以哪怕当前可执行文件没调用它,也会保留它的section和符号。
这就是为什么unused_in_static会出现在最终的app里。
3. 第二种链接顺序:app.o libshared.so libstatic.a
同样拆解流程:
app.o还是有use_shared和use_static两个未定义符号。- 链接器处理
libshared.so:这个库不仅有use_shared的定义,而且因为编译它的时候链接了libstatic.a,所以也包含use_static的定义。这下两个未定义符号都解决了。 - 链接器处理
libstatic.a:这时候已经没有未定义符号需要解决了,所以链接器直接忽略这个静态库,根本不会提取任何目标文件。 - 执行
-Wl,-gc-sections:static.o都没被加入链接,自然不会有unused_in_static的符号。
4. 前两种链接顺序是否符合规范?
完全符合规范!链接器按命令行顺序处理输入文件、静态库只提取必要的目标文件,都是链接器的标准行为,没有任何不规范的地方。不同链接顺序导致不同结果,是链接器设计逻辑的必然产物,不是bug。
额外小技巧:让第一种顺序也能移除未使用函数
如果想保留第一种链接顺序,同时移除unused_in_static,可以在编译时加上-fvisibility=hidden选项,把所有函数的默认可见性设为隐藏。这样链接器就会认为这些符号不会被外部模块引用,-Wl,-gc-sections就能正常移除未被调用的unused_in_static了。
内容的提问来源于stack exchange,提问作者peper0
相关产品推荐
相关产品推荐

