正常链接过程
假设一个工程里有两个源码文件和两个静态库:
- main.m
实现了程序入口main
,它又调用了未知方法Fun1
和Fun2
- object.m
实现了方法Fun1
和Fun3
,其中Fun1
调用了未知方法Fun4
- libA.a
包含两个目标文件,第一个实现了Fun2
和Fun5
,第二个实现了Fun6
- libB.a
包含一个目标文件,它实现了Fun2
和Fun4
下图是链接过程:
- 解析所有目标文件,生成一个个符号图(符号图用来表示各符号间的引用关系,紫色节点表示未定义符号)
- 将所有的符号图合并成一张符号主图(master graph),中间会用已定义符号替换掉同名的未定义符号。同时生成一张全局符号表,用于记录每个符号的状态。
- 若生成的符号主图中仍存在未定义的符号,则按顺序扫描静态库,否则跳转第6步直接生成可执行文件。
- 静态库其实就是带有符号目录的目标文件集合。若符号目录匹配到了主图所需符号,则从静态库里找到定义该符号的目标文件,并把它所有的符号都合并到主图和全局符号表中,过程中会替换掉已有的同名符号(包括已定义的)。重复执行第3步和第4步,如果所有静态库都扫描完了,仍然存在未定义符号,则报符号找不到的错误
symbol not found
- 如果有需要(一般Release下才做),会进行死代码剥离的操作,即从主图入口(main)开始遍历,不能访问到的节点就是无效可剥离的符号。
- 将主图中的符号都写入可执行文件中去。 每个符号在可执行文件中的地址顺序,与输入文件的先后顺序保持一致。
经典链接错误
symbol not found
报错时机:链接过程中扫描完所有的静态库和动态库后,仍然存在未定义的符号。
解决方法:工程中添加定义该符号的静态库,同时添加正确的静态库搜索路径。-
duplicated symbol
报错时机:- 如果
main.o
和object.o
实现了同名方法,那链接过程中合并符号图时会报错。 - 如果
object.o
和libA.a
中实现了同名方法,而且libA.a
使用了-force_load
描述,它会强行加载libA
中所有的目标文件,导致符号冲突。 - 多个静态库实现了同名方法,全局用了'-all_load'或者各自都用了'-force_load',会导致符号冲突。
- 如果
-ObjC
这个flag告诉链接器把库中定义的Objective-C类和Category都加载进来。这样编译之后的app会变大(因为加载了其他的objc代码进来)。但是如果静态库中有类和category的话只有加入这个flag才行。
-all_load
这个flag是专门处理-ObjC的一个bug的。用了-ObjC以后,如果类库中只有category没有类的时候这些category还是加载不进来。变通方法就是加入-all_load或者-force-load。-all_load会强制链接器把目标文件都加载进来,即使没有objc代码。
注意:假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件
-force_load
这个flag所做的事情跟-all_load其实是一样的,只是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载 ,-force_load在xcode3.2后可用。
解决方法:
- 同工程源码中不能定义多个同名方法,可以通过修改方法前缀、修改命名空间(宏替换也可)、使用static方法等方式
- 谨慎使用'-Objc"、"-all_load"、"-force_load',它常用来强制加载OC的category,对于纯C++库往往没多大意义。
- 多个静态库冲突时,如果你是第三方库的提供者,应该用使用prelink等方式减少无用符号的导出(参考如何隐藏SDK符号)。如果你只是第三方库使用者(无源码),有一种风险较大的方法就是在其中一个静态库里剔除冲突的目标文件。