为什么 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

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

21208 次浏览

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

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

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

因此,如果使用 -l包含某个库,链接器将使用该库解析尽可能多的当前挂起的符号,然后完全忘记该库。如果 回见突然发现它现在需要从该库获取一些额外的对象文件,链接器将不会“返回”到该库来检索这些额外的对象文件。已经太迟了。

出于这个原因,在链接器的命令行中使用 -l选项 很晚了总是一个好主意,这样当链接器到达 -l时,它就可以可靠地确定它需要和不需要哪些对象文件。将 -l选项作为链接器的第一个参数通常没有任何意义: 在最开始的时候,挂起的符号列表是空的(或者,更确切地说,由单个符号 main组成) ,这意味着链接器不会从库中获取任何东西。

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

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

有时,当两个(或更多)库以循环方式相互引用时,可能需要对同一个库使用两次 -l选项,以便给链接器两次机会从该库检索必要的对象文件。

我刚刚按了 同样的问题。底线是 gnu 工具不会总是在库列表中“搜索回来”来解决丢失的符号。简单的解决办法有以下任何一种:

  1. 只需按照依赖顺序指定 lib 和 object (如上所述)

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

    gcc foo.c -lfoo -lbar -lfoo
    
  3. Use the -( and -) params to specify a group of archives that have such circular dependencies. Look at the GNU linker manual for --start-group and --end-group. See here for more details.

When you use option 2 or 3, you're likely introducing a performance cost for linking. If your don't have that much to link, it may not matter.

或者使用 重新扫描

来自 Oracle Solaris 11.1链接器和库指南的 pg 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