为什么gcc中'-l'选项的顺序很重要?[重复]


72

我正在尝试编译一个使用udis86库的程序。实际上,我正在使用库用户手册中给出的示例程序。但是在编译时会产生错误。我得到的错误是:

我正在使用的命令是:

按照用户手册中的指示。

显然,链接器无法链接libudis库。但是,如果我将命令更改为:

它开始工作。因此,请有人解释第一个命令的问题是什么?

c  gcc  linker  ld 

什么版本的gcc?这可能是与版本有关的错误。
EnabrenTane 2012年

1
它不是一个bug!版本是:gcc(Ubuntu / Linaro 4.4.4-14ubuntu5.1)4.4.5

Answers:


107

因为这就是GNU链接器使用的链接算法的工作方式(至少在链接静态库时)。链接器是一个单通道链接器,一旦看到它们就不会重新访问库。

库是目标文件的集合(归档)。使用该-l选项添加库时,链接程序不会无条件地从库中获取所有目标文件。它仅采用当前需要的那些目标文件,即解析一些当前未解析(待定)符号的文件。之后,链接器完全忘记了该库。

当链接器处理输入的目标文件时,链接器会不断维护挂起的符号列表,从左到右一个接一个。在处理每个目标文件时,一些符号被解析并从列表中删除,其他新发现的未解析符号被添加到列表中。

因此,如果您通过使用包含了一些库-l,则链接程序将使用该库来解析尽可能多的当前挂起的符号,然后完全忽略该库。如果以后突然发现它现在需要该库中的一些其他目标文件,则链接器将不会“返回”该库以检索那些其他目标文件。已经为时已晚。

由于这个原因,在链接器命令行的后期使用-l选项始终是一个好主意,这样,当链接器到达该链接器时,它就可以可靠地确定所需的对象文件和不需要的对象文件。将选项作为链接器的第一个参数放置通常根本没有任何意义:在开始时,挂起的符号列表为空(或更准确地说,由单个符号组成),这意味着链接器不会从中获取任何内容图书馆。-l-lmain

在你的情况,你的目标文件example.o包含对符号的引用ud_initud_set_input_file等接头应首先接收目标文件。它将这些符号添加到未决符号列表中。之后,您可以使用-l选项添加库:-ludis86。链接器将搜索您的库并从中获取所有可解析那些未决符号的内容。

如果将-ludis86选项首先放在命令行中,则链接器将有效地忽略您的库,因为在开始时它并不知道将需要它ud_initud_set_input_file等等。稍后,在处理example.o时它将发现这些符号并将它们添加到挂起的符号清单。但是这些符号将一直无法解析到最后,因为它们-ludis86已经被处理(有效地被忽略了)。

有时,当两个(或多个)库以循环方式互相引用时,一个库甚至可能需要对-l同一库使用两次该选项,以使链接程序有两次机会从该库中检索必要的目标文件。


16
这不仅仅是GNU的事情。这是POSIX所需的标准行为:-l库搜索名为liblibrary.a的库。遇到库名称时,应搜索该库,因此-l选项的位置很重要。如扩展说明部分所述,可以用这种方式指定几个标准库。实现可以将.a以外的实现定义的后缀识别为库。参见pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html –R。GitHub
停止帮助ICE,

3
@R ..这是一个问题,为什么标准要求这种行为?使用这种方法是否有一些优势?其他编译器工具,例如msvc和borland都不遵循这种方法,并且可以正常工作。从许多方面来看,它似乎更好,因为该工具的用户不太容易出错。
greatwolf

1
@greatwolf:就C语言而言,MSVC与“工作正常”恰好相反。无论如何,顺序重要的动机是,可以在多个库中定义相同的符号,在这种情况下,希望能够控制使用哪一个。
R .. GitHub停止帮助ICE,2013年

1
我的印象是,这不仅是静态库问题,例如,如果您明确指定-l:libwhatever.so,只要-l:libwhatever.so令牌在gcc命令中早于gcc命令出现,则未定义的引用链接器错误将持续存在。 object_file.o令牌
Alexandr iolov 2014年

3
您可能想在GNU的组中添加一个段落ld。见--start-group--end-groupld(1)手册页。它有效地告诉链接器重新访问该组中的档案。
jww

9

我前一段时间也遇到同样的问题。最重要的是,gnu工具不会总是在库列表中“向后搜索”以解决丢失的符号。简单修复是以下任何一种:

  1. 只需按依赖关系顺序指定libs和objs(如您在上面发现的)

  2. 或者,如果您具有循环依赖关系(其中libA引用libB中的一个函数,而libB引用libA中的一个函数),则只需在命令行上两次指定lib。这也是手册页建议的内容。例如

  3. 使用-(-)参数可指定一组具有此类循环依赖性的档案。请参阅GNU链接器手册中的--start-group--end-group。有关更多详细信息,请参见此处

当使用选项2或3时,可能会引入性能成本。如果您没有太多链接,那就没关系了。


4

或使用重新扫描

从《Oracle Solaris 11.1链接器和库指南》的第41页起:

档案之间可能存在相互依赖性,因此必须通过从另一档案中提取成员来解决从一个档案中提取成员的问题。如果这些依赖关系是循环的,则必须在命令行上重复指定档案,以满足先前的引用。

重复存档规范的确定和维护可能很繁琐。

-z rescan-now选项使此过程更简单。当在命令行上遇到-z rescan-now选项时,链接编辑器会立即处理该选项。在此选项之前,已从命令行处理过的所有存档都将立即重新处理。此处理尝试找到其他解析符号引用的存档成员。存档重新扫描将继续进行,直到发生遍历存档列表的情况,其中没有新成员被提取。前面的示例可以简化如下。

或者,可以使用-z rescan-start和-z rescan-end选项将相互依赖的归档分组到一个归档组中。当在命令行上遇到结束定界符时,链接编辑器会立即重新处理这些组。对在组中找到的归档进行重新处理,以尝试查找可解决符号引用的其他归档成员。存档重新扫描将继续进行,直到发生遍历存档组的情况,在该存档组中不会提取新成员。使用归档组,可以将前面的示例编写如下。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.