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

时间:2012-08-10 00:42:29

标签: c gcc linker ld

我正在尝试编译一个使用udis86库的程序。实际上我正在使用库中user-manual给出的示例程序。但是在编译时,它会给出错误。我得到的错误是:

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 

它开始工作。那么可以请某人解释第一个命令的问题是什么?

3 个答案:

答案 0 :(得分:97)

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

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

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

因此,如果您使用-l包含了一些库,链接器将使用该库来尽可能多地解析当前挂起的符号,然后完全忘记该库。如果稍后突然发现它现在需要来自该库的一些额外的目标文件,则链接器将不“返回”到该库以检索那些额外的目标文件。已经太晚了。

出于这个原因,在链接器的命令行中使用-l选项 late 总是一个好主意,这样当链接器到达-l时它可以可靠地确定它需要哪些目标文件以及它不需要哪些目标文件。将-l选项作为第一个参数放置到链接器通常完全没有意义:在开始时,挂起符号列表为空(或者更确切地说,由单个符号main组成) ,这意味着链接器根本不会从库中获取任何内容。

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

如果首先在命令行中放置-ludis86选项,链接器将有效忽略您的库,因为在开始时它不知道它将需要{{1}稍后,处理ud_init时,它会发现这些符号并将它们添加到待处理的符号列表中。但是这些符号仍然没有得到解决,因为ud_set_input_file已经处理过(并且被有效忽略)。

有时,当两个(或更多)库以循环方式相互引用时,甚至可能需要对同一个库使用example.o选项两次,以便为链接器提供两次机会来检索必要的目标文件来自那个图书馆。

答案 1 :(得分:8)

我回来了this same issue。底线是gnu工具不会总是在库列表中“回搜”以解决丢失的符号。简单修复可以是以下任何一种:

  1. 只需在依赖关系顺序中指定libs和objs(如上所述)

  2. 如果你有循环依赖(其中libA引用libB中的函数,但libB引用libA中的函数),则只需在命令行上指定两次libs。这也是手册页的建议。 E.g。

    gcc foo.c -lfoo -lbar -lfoo
    
  3. 使用-(-)参数指定一组具有此类循环依赖关系的归档。查看--start-group--end-group的GNU链接器手册。有关详细信息,请参阅here

  4. 使用选项2或3时,您可能会为链接引入性能成本。如果您没有那么多链接,那可能无关紧要。

答案 2 :(得分:4)

或使用重新扫描

来自Oracle Solaris 11.1 Linkers and Libraries Guide的第41页:

  

档案之间可以存在相互依存关系,以便提取   必须通过提取成员来解析一个存档中的成员   从另一个档案。如果这些依赖项是循环的,则归档   必须在命令行上重复指定以满足之前的要求   引用。

$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA 
  

重复存档规范的确定和维护可以   很乏味。

     

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

$ cc -o prog .... -lA -lB -lC -z rescan-now 
  

或者,可以使用-z rescan-start和-z rescan-end选项进行分组   相互依赖的归档一起成为归档组。这些   在结束时,链接编辑器会立即对组进行重新处理   在命令行上遇到分隔符。在档内找到档案   对该组进行重新处理以尝试查找其他存档   解析符号引用的成员。此存档重新扫描   继续,直到传递归档组,其中没有新的   成员被提取。使用归档组,前面的示例可以   写如下。

$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end