【问题标题】:Passing `-l<libname>` vs passing `lib<libname>.a` directly to linker?传递 `-l<libname>` 与将 `lib<libname>.a` 直接传递给链接器?
【发布时间】:2019-08-06 20:21:51
【问题描述】:

假设我有两个文件

// a.c
int a() {return 1;}

// b.c
int a();
int b() {return a();}

我将它们分别编译为a.ob.o

为了创建一个可执行或共享库,可以调用gcc a.o b.o -o libab.so -shared。但我也注意到,也可以调用gcc b.o -L. -l:a.o -o libab.so -shared 来生成(显然)相同的输出。令我惊讶的是,即使运行gcc a.o -L. -l:b.o -shared 也会生成一个同时包含a()b() 的库。 (链接器不应该丢弃未使用的库b.o,因为a.o 不依赖它吗?)

后两个大概传递a 就好像a.o 是一个库一样。现在,如果我运行 ar rcs liba.a a.ogcc b.o -L. -l:liba.a -sharedgcc b.o liba.a -shared 都运行没有任何问题并给出相同的输出。

但是,我也看到了这种技巧不起作用并导致未定义引用的情况。因此,正如标题所说,我的问题是:将对象作为库和普通对象文件传递有什么区别,在 C++ 方面有什么区别?

问题出现在一个更大的项目中。抱歉缺少 mcve,因为我似乎无法隔离问题。

【问题讨论】:

  • 我想,这里有一些误解。静态库只是 .o 文件的存档,因此链接器可以将单个 .o 文件视为库也就不足为奇了。它本身不应导致任何未定义的行为。恐怕,您必须在隔离测试用例上做更多工作。
  • @SergeyA,我知道.a.o 文件的存档。为简单起见,假设.a.o,那么问题是gcc a.o b.ogcc a.o -L. -l:b.o 有何不同。
  • 这正是我所说的,没有真正的区别。另外,-l:&lt;lib&gt; 是我不熟悉的语法,我在任何地方都找不到它的引用。我认为它有效,但我很确定这不是传统的。
  • results undefined references - 这到底是什么意思?
  • 您知道库的顺序在链接器调用中很重要吗?也许这可能是一个答案?使用 .o 文件时,符号的顺序无关紧要,而对于 .so 文件,它很重要?

标签: c++ c unix linker


【解决方案1】:

[如何] 将-l&lt;libname&gt; 与 [不同] 直接将lib&lt;libname&gt;.a 传递给链接器?

传递-llibname.so 将使GNU 链接器在搜索符号时仅遍历库一次(不在--whole-archive 选项之后)。将.a 文件直接指定给链接器会使其在.a 文件内的所有目标文件中为每个符号搜索每个符号,而不仅仅是一次。

来自GCC Linker options(强调我的):

-图书馆

...

在命令的哪个位置编写此选项会有所不同;链接器按照指定的顺序搜索和处理库和目标文件。因此,“foo.o -lz bar.o”在文件 foo.o 之后但在 bar.o 之前搜索库“z”。如果 bar.o 引用了 ‘z’ 中的函数,这些函数可能不会被加载。

来自binutils ld options

-l 名称规范

...

链接器只会在命令行中指定的位置搜索存档一次。如果存档定义了在命令行上出现在存档之前的某个对象中未定义的符号,则链接器将包含存档中的适当文件。但是,稍后在命令行中出现的对象中未定义的符号不会导致链接器再次搜索存档。

【讨论】:

  • 传递 -llibname.so 将使 GNU 链接器仅遍历库一次... 将 .a 文件直接指定给链接器使其搜索所有符号.a 文件中的目标文件。我没有看到区别,实际上我不相信有 区别 - 我一直认为指定 -lx 与(比如说)/lib/libx.a 的行为相同。
【解决方案2】:

将对象作为库传递和作为普通对象文件传递有什么区别,在 C++ 方面有什么区别?

这取决于实施。在最一般的意义上,Unix 风格的链接器,例如您在库搜索路径中询问通过 -l 选项命名的 搜索 对象,而如果您直接命名文件,则必须指定确切的文件。

此外,如果您使用-l 选项来指定要链接的文件,那么在一般情况下,链接器会通过在参数前面加上“lib”并附加“.a”来构造文件名,或者以其他方式方式,例如同时搜索或代替“.so”文件。 (当参数的第一个字符是 : 时,您似乎正在使用的 GNU 链接器对此行为提供了一个例外。在这种情况下,它将参数的其余部分作为确切的文件名,并搜索它。)

许多链接器还接受在命令行上指定的显式库名称(例如 libfoo.a 而不是 -lfoo),因此这些链接器需要能够确定每个文件的类型。通常这是通过检查文件,而不是依赖其名称。至少,GNU ld 将此文件类型检测扩展到通过 -l 选项指定的文件。

在命令行上以任何特定形式指定对象和库的顺序对典型的链接器实现很重要。例如,the docs for GNU ld 指定

引用文件的选项,例如“-l”或“-T”,会导致文件 在选项出现在命令行中的位置读取, 相对于目标文件和其他文件选项

这很重要,因为

链接器只会在存档位置搜索一次 在命令行中指定。如果档案定义了一个符号 在存档之前出现的某些对象中未定义 在命令行上,链接器将包含适当的文件 从档案中。但是,对象中出现未定义的符号 稍后在命令行上不会导致链接器搜索 再次存档。

当然

您可以在命令行上多次列出同一个存档。

文档对此并不完全清楚,但根据经验,上述“存档”一词的使用意义重大。它实际上只有归档 文件——静态库——适用于“仅搜索一次”条款。大致而言,GNU 链接器命令行上不同普通目标文件和共享库的相对顺序,无论如何指定,都不会影响符号解析。

所以是的,您是否为 (GNU) 链接器指定常规目标文件或静态档案或共享库确实很重要,它们的顺序在某种程度上很重要,但是您指定的 方式它们无关紧要。

我还看到了这种技巧不起作用并导致未定义引用的情况。

对于 GNU 链接器,这将是因为真正缺少库或对象,或者是因为静态存档相对于其他对象文件或存档的顺序不合适。其他一些链接器更敏感。

【讨论】:

    【解决方案3】:

    简短回答:

    1. -L-l 选项提供了查找库存档(和共享库)的快捷方式。但是,一旦您使用-l 定位库(在标准位置,或在-L 指定的位置),读取该库的方式与您指定其文件名时的读取方式相同(例如/lib/libx.a) 显式在命令行的同一位置。

    2. 当您指定单个对象 (.o) 文件时,将无条件加载该文件的全部内容。当您指定库存档 (.a) 文件时,仅加载其中那些必要的对象(以满足未定义的未定义引用)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-25
      • 2023-04-08
      • 1970-01-01
      • 2023-03-31
      相关资源
      最近更新 更多