为什么gcc中'-l'选项的顺序很重要?
我想编译一个使用udis86库的程序。 其实我正在使用图书馆用户手册中给出的示例程序。 但是在编译时,它会给出错误。 我得到的错误是:
example.c:(.text+0x7): undefined reference to 'ud_init' example.c:(.text+0x7): undefined reference to 'ud_set_input_file' . . example.c:(.text+0x7): undefined reference to 'ud_insn_asm'
我正在使用的命令是:
$ gcc -ludis86 example.c -o example
如用户手册中的说明。
显然,链接器不能链接libudis库。 但如果我改变我的命令:
$ gcc example.c -ludis86 -o example
它开始工作。 那么可以请某人解释第一个命令有什么问题?
因为这就是GNU链接器使用的链接algorithm的工作原理(至less在链接静态库时)。
库是对象文件的集合(存档)。 使用-l
选项添加库时,链接器不会无条件地从库中获取所有对象文件。 它只需要那些当前需要的对象文件,即parsing一些当前未parsing(待定)符号的文件。 之后,链接器完全忘记了那个库。
当链接器从左向右依次处理input对象文件时,链接器持续维护挂起符号列表。 当它处理每个目标文件时,一些符号被parsing并从列表中移除,其他新发现的未parsing符号被添加到列表中。
因此,如果使用-l
包含了一些库,链接器就会使用该库来尽可能多地parsing当前挂起的符号,然后彻底忘记该库。 如果以后突然发现它现在需要来自该库的一些额外的目标文件,链接器将不会“返回”到该库来检索这些额外的目标文件。 现在已经太晚了。
出于这个原因,在链接器的命令行后面使用-l
选项总是一个好主意,所以当链接器到达-l
它可以可靠地确定它需要哪个目标文件,哪个目标文件不需要。 将-l
选项作为链接器的第一个参数通常是毫无意义的:一开始挂起的符号列表是空的(或者更准确地说,由单个符号main
),这意味着链接器不会从图书馆采取任何东西。
在你的情况下,你的目标文件example.o
包含对符号ud_init
, ud_set_input_file
等的引用。链接器应该首先接收该目标文件。 它会将这些符号添加到挂起符号列表中。 之后,您可以使用-l
选项来添加您的库: -ludis86
。 链接器将search你的库,并从中解决那些待处理的符号。
如果在命令行中首先放置-ludis86
选项,链接器将会有效地忽略你的库,因为一开始它并不知道它需要ud_init
, ud_set_input_file
等等。稍后,在处理example.o
它会发现这些符号并将它们添加到挂起的符号列表中。 但是这些符号到最后仍然没有解决,因为-ludis86
已经被处理(并且被有效地忽略)。
有时,当两个(或更多)库以循环方式彼此引用时,甚至可能需要在同一个库中使用两次-l
选项,以使链接器有两次机会从该库中检索必要的对象文件。
我又碰到同样的问题 。 底线是gnu工具不会总是在库列表中“search”以解决缺失的符号。 简单的修复是以下任何一种:
-
只需按照依赖顺序指定libs和objs(如上所述)
-
或者如果你有循环依赖(libA引用函数,而libB引用libA中的函数),那么只需在命令行上指定libs两次。 这也是手册页面所build议的。 例如
gcc foo.c -lfoo -lbar -lfoo
-
使用
-(
和-)
参数来指定一组具有这种循环依赖性的归档。 查看GNU链接器手册--start-group
和--end-group
。 在这里看到更多的细节。
当您使用选项2或3时,您可能会引入链接的性能成本。 如果你没有那么多的链接,这可能无关紧要。
或者使用重新扫描
从Oracle Solaris 11.1链接程序和库指南的第41页开始:
档案之间的相互依存关系可以存在,这样从一个档案中提取成员必须通过从另一个档案中提取成员来解决。 如果这些依赖关系是循环的,则必须在命令行上重复指定归档以满足之前的引用。
$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA
重复存档规范的确定和维护可能是乏味的。
-z重新扫描,现在选项使这个过程更简单。 在命令行中遇到选项时,立即通过链接编辑器处理-z重新扫描即时选项。 在此选项之前从命令行处理的所有存档都会立即进行重新处理。 此处理尝试查找parsing符号引用的其他归档成员。 重新扫描归档文件,直到发生没有提取新成员的归档文件清单。 前面的例子可以简化如下。
$ cc -o prog .... -lA -lB -lC -z rescan-now
或者,可以使用-z重新扫描开始和-z重新扫描结束选项将相互依赖的归档组合到归档组中。 在命令行中遇到closures分隔符时,将立即通过链接编辑器对这些组进行重新处理。 在组中find的归档被重新处理,试图find解决符号引用的额外归档成员。 此归档重新扫描将继续,直到发生没有提取新成员的归档组为止。 使用存档组,前面的例子可以写成如下。
$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end