【问题标题】:Why does the order of '-l' option in gcc matter? [duplicate]为什么 gcc 中“-l”选项的顺序很重要? [复制]
【发布时间】:2012-08-07 07:16:27
【问题描述】:

我正在尝试编译一个使用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 

它开始工作。那么有人可以解释第一个命令有什么问题吗?

【问题讨论】:

  • 什么版本的 gcc?这可能是与版本相关的错误。
  • 这不是错误!版本为:gcc (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5

标签: c gcc linker ld


【解决方案1】:

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

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

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

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

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

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

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

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

【讨论】:

  • 这不仅仅是 GNU 的东西。这是标准的 POSIX 要求的行为:-l library 搜索名为 liblibrary.a 的库。遇到库名称时应搜索库,因此放置 -l 选项很重要。可以用这种方式指定几个标准库,如扩展描述部分所述。实现可以将 .a 以外的实现定义的后缀识别为表示库。参见pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html
  • @R.. 这引出了一个问题,为什么标准需要这种行为?使用这种方法有什么好处吗?其他编译器工具,如 msvc 和 borland 不遵循这种方法,它工作得很好。在许多方面,它似乎更好,因为它对这个工具的用户来说不太容易出错。
  • @greatwolf:就 C 而言,MSVC 与“工作得很好”正好相反。无论如何,重要的顺序的动机是您可以在更多中定义相同的符号而不是一个库,在这种情况下,您希望能够控制使用哪个库。
  • 我的印象是,这不仅仅是一个静态库问题,例如,如果您明确指定 -l:libwhatever.so,只要 -l:libwhatever.so 标记,未定义的引用链接器错误就会持续存在在 gcc 命令中比 object_file.o 令牌更早发生
  • 您可能想在 GNU 的 ld 中添加一个关于组的段落。参见ld(1) man page 中的--start-group--end-group。它有效地告诉链接器重新访问组中的档案。
【解决方案2】:

我不久前打了this same issue。底线是 gnu 工具不会总是在库列表中“搜索”来解决丢失的符号。简单的修复方法如下:

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

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

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

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

【讨论】:

    【解决方案3】:

    或使用重新扫描

    来自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
    

    【讨论】:

      猜你喜欢
      • 2015-12-01
      • 2013-11-18
      • 2015-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多