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

69 c linker gcc ld

我正在尝试编译一个使用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'
Run Code Online (Sandbox Code Playgroud)

我使用的命令是:

$ gcc -ludis86 example.c -o example 
Run Code Online (Sandbox Code Playgroud)

按照用户手册中的说明进行操作.

显然,链接器无法链接libudis库.但如果我改变命令:

$ gcc example.c -ludis86 -o example 
Run Code Online (Sandbox Code Playgroud)

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

AnT*_*AnT 100

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

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

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

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

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

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

如果将-ludis86第一个选项在命令行中,连接器将有效地忽略你的图书馆,因为在开始的时候不知道,这将需要ud_init,ud_set_input_file等等.后来,在处理时example.o会发现这些符号,并将其添加到待处理的符号名单.但是这些符号最终仍未解决,因为-ludis86已经处理过(并且被有效忽略).

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

  • 它不仅仅是一个GNU的东西.这是标准的,POSIX所需的行为:*-l library搜索名为liblibrary.a的库.遇到名称时应搜索库,因此-l选项的位置很重要.可以用这种方式指定几个标准库,如扩展描述部分所述.实现可以识别除.a之外的实现定义的后缀,表示库.*请参阅http://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html (16认同)
  • @R ..这引出了一个问题,为什么标准需要这种行为?使用这种方法有一些优势吗?像msvc和borland这样的其他编译器工具不遵循这种方法,它可以正常工作.在许多方面,它似乎更好,因为这个工具的用户不太容易出错. (3认同)
  • 你可能想在GNU的`ld`中添加一个关于组的段落.请参阅[`ld(1)`手册页]中的`--start-group`和`--end-group`(https://linux.die.net/man/1/ld).它有效地告诉链接器重新访问组中的存档. (2认同)

sel*_*bie 8

我前段时间遇到同样的问题.底线是gnu工具不会总是在库列表中"回搜"以解决丢失的符号.简单修复可以是以下任何一种:

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

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

    gcc foo.c -lfoo -lbar -lfoo
    
    Run Code Online (Sandbox Code Playgroud)
  3. 使用-(-)params指定一组具有此类循环依赖关系的归档.看看为GNU连接器的手动--start-group--end-group.有关详细信息,请参见此处

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


fle*_*erb 5

或者使用重新扫描

来自《Oracle Solaris 11.1 链接器和库指南》第 41 页:

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

$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA 
Run Code Online (Sandbox Code Playgroud)

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

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

$ cc -o prog .... -lA -lB -lC -z rescan-now 
Run Code Online (Sandbox Code Playgroud)

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

$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end
Run Code Online (Sandbox Code Playgroud)