GCC LD对依赖库的输入顺序敏感
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的算法则可最大限度地减少文件顺序对链接的影响。