LD 在链接生成目标文件时,会从左到有扫描输入的依赖库,当依赖库之间也有依赖关系时,必须将「依赖别人的库」放在「被别人依赖的库」的前面。否则会链接失败!失败的症状有:
- 如果编译目标是可执行文件,将直接报链接错误,会出来一大堆函数定义找不到的错误。
- 如果编译目标是动态链接库,则会在运行时,才爆出无法找到函数定义的错误。这个错误更要命。
举例: 有三个库a.a
,b.a
,c.a
。其中b.a
依赖了a.a
和c.a
,a.a
和c.a
相互不依赖,而target
依赖了a.a
和b.a
。则
g++ -o target target.cpp a.a b.a c.a // 成功,
g++ -o target target.cpp a.a c.a b.a // 失败, 因为c.a应该在b.a后面
为什么会这样,这里的解释比较清楚:
在符号解析(symbol resolution)阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合:
- 集合 E 是将被合并到一起组成可执行文件的所有目标文件集合;
- 集合 D 是所有之前已被加入 E 的目标文件定义的符号集合;
- 集合 U 是未解析符号(unresolved symbols ,即那些被 E 中目标文件引用过但在 D 中还不存在的符号)的集合。
一开始, E、D、U 都是空的。
- 对命令行中的每一个输入文件 f ,链接器确定它是目标文件还是库文件,如果它是目标文件,就把 f 加入到 E ,并把 f 中未解析的符号和已定义的符号分别加入到 U、D 集合中,然后处理下一个输入文件。
- 如果 f 是一个库文件,链接器会尝试把 U 中的所有未解析符号与 f 中各目标模块定义的符号进行匹配。如果某个目标模块 m 定义了一个 U 中的未解析符号,那么就把 m 加入到 E 中,并把 m 中未解析的符号和已定义的符号分别加入到 U、D 集合中。不断地对 f 中的所有目标模块重复这个过程直至到达一个不动点(fixed point),此时 U 和 D 不再变化。而那些未加入到 E 中的 f 里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。
- 当扫描完所有输入文件时如果 U 非空或者有同名的符号被多次加入 D ,链接器报告错误信息并退出。否则,它把 E 中的所有目标文件合并在一起生成可执行文件。
上述规则针对的是 Unix 平台链接器,而 VC(至少 VC6.0)linker 则有相当的不同: 它首先依次处理命令行中出现的所有目标文件,然后依照顺序不停地扫描所有的库文件,直至 U 为空或者某遍(从头到尾依次把所有的库文件扫描完称为一遍)扫描过程中 U、D 无任何变化时结束扫描,此刻再根据 U 是否为空以及是否有同名符号重复加入 D 来决定是出错退出还是生成可执行文件。很明显 Unix 链接器对输入文件在命令行中出现的顺序十分敏感,而 VC 的算法则可最大限度地减少文件顺序对链接的影响。
Q. E. D.