制作库时,ranlib、ar和ld有什么区别?

42

在我的项目(遗留代码)中,我注意到了两种使用*.o文件生成C++/Unix库的方法:

ar qc libgraphics.a *.o
ranlib libgraphics.a

ld -r -o libgraphics.a *.o

这两种方法有何不同,应该分别用于什么目的?


2
ar 创建或更新库。ranlib 重新生成其索引(您可以进行多次更新,然后运行 ranlib 一次)。ld 创建完全不同类型的库(我认为给它们 .a 后缀是错误和误导性的;在您的情况下,ld 的输出是一个合并的对象文件,应该被赋予 .o 后缀)。 - n. m.
1个回答

107

ar

在Linux中,ar是GNU通用的归档工具。除了GNU版本的ar之外,在其他类Unix操作系统中还存在非GNU的变体。使用选项c

ar c... archive-name file...

它创建一个包含file...副本的存档。惯常但不一定要使用扩展名.a(表示archive)来命名archive-name。每个file...可以是任意类型的文件,不一定是对象文件。

当所存档的文件都是目标文件时,通常的意图是将该选择的目标文件交付给程序或DSO(Dynamic Shared Objects)的链接。在这种情况下,archive-name也通常被赋予前缀lib,例如libfoo.a,以便可以通过链接器选项-lfoo发现它作为候选链接器输入文件。

用作链接器输入文件时,libfoo.a通常被称为静态库。这种用法经常让没有经验的程序员感到困惑,因为它让他们认为存档libfoo.a与DSO(libfoo.so, 通常称为动态/共享库)非常相似,并根据此基础构建错误的期望。实际上,“静态库”和“动态库”并不相似,并且在链接中使用方式完全不同。

显着的区别是静态库不是由链接器而是由ar创建的。因此,没有链接发生,也没有符号解析发生。存档的目标文件保持不变:它们只是被放置在一个包中。

当在链接某些由链接器产生的东西(例如程序或DSO)时,输入一个存档,链接器在包中查找是否有提供先前链接中出现的未解决的符号引用的对象文件。如果找到了任何一个,它就会从包中提取这些目标文件,并将它们像单独命名的那样链接到输出文件中,而完全不提及存档。因此,存档在链接中的整个作用就是作为一组对象文件的包,在这些文件中,链接器可以选择需要继续链接的文件。

默认情况下,GNU ar使其输出存档可以用作链接器输入。它向存档添加一个虚假的"文件",带有一个幻想的文件名,并在这个虚假的文件中写入内容,链接器能够从其中读取关于由存档中任何对象文件定义的全局符号的查找表,以及这些对象文件在存档中的名称和位置。这个查找表使得链接器能够查找存档并识别任何定义它手头未解决的符号引用的对象文件。

您可以使用q(=quick)选项抑制该查找表的创建或更新,您在自己的ar示例中也使用了它,并且还可以使用(大写的)S(=无符号表)选项。如果您调用ar来创建或更新一个由于任何原因而没有(最新)符号表的存档,那么您可以使用s选项为其提供一个符号表。

ranlib

ranlib 不会创建库文件。在Linux中,ranlib 是一个遗留程序,如果一个 ar 归档没有符号表,它会添加一个(最新的)符号表。它的效果与使用 GNU arar s 命令完全相同。在历史上,在 ar 程序自身无法生成符号表之前,ranlib 是注入魔术伪文件到归档文件中,使链接器能够从中提取目标文件的技巧。在非GNU类Unix-like操作系统中,可能仍然需要使用 ranlib 进行此操作。例如:

ar qc libgraphics.a *.o
ranlib libgraphics.a

says:

  • 将当前目录中所有*.o文件无符号表地追加到一个归档文件中,创建 libgraphics.a
  • 然后添加符号表到 libgraphics.a

在Linux中,这与以下命令等效:

ar cr libgraphics.a *.o
ar qc libgraphics.a *.o创建的档案是没有符号表的,因此链接器无法使用。 ld 您的示例:
ld -r -o libgraphics.a *.o

这实际上是相当非正统的。这说明了链接器(ld)的相当罕见的用法,通过将多个输入文件链接到单个输出目标文件中来生成一个合并的目标文件,在其中进行了尽可能的符号解析,给定了输入文件。 -r(=可重定位)选项指示链接器通过尽可能地链接输入文件并在输出文件中保留未定义的符号引用来产生对象文件目标(而不是程序或DSO),并且不会因为未定义的符号引用在输出文件中而失败。这种用法称为部分链接。

ld -r ... 的输出文件是一个对象文件,而不是一个ar存档文件,指定一个看起来像ar存档的输出文件名并不能使它变成一个ar存档文件。所以你的例子是欺骗性的:

ld -r -o graphics.o *.o

这种欺骗行为目的不明,因为即使一个 ELF 目标文件被称为 libgraphics.a,并且通过该名称或 -lgraphics 输入到链接中,链接器也会正确地将其识别为 ELF 目标文件,而不是 ar 存档文件,并将其按照命令行中任何对象文件的方式无条件链接到输出文件中;而输入真正的存档库的目的是只在引用它们的条件下链接存档成员。也许你在这里只是看到了一个错误链接的例子。

总结...

我们实际上只看到了一种生产传统意义上所称的 的方法,那就是通过对一些目标文件进行归档并在存档中放置符号表来生成所谓的 静态库

我们完全没有看到如何生产另一种和最重要的被传统上称为的东西,即动态共享对象/共享库/动态库。

像程序一样,DSO 是由 链接器(linker) 生成的。程序和 DSO 是可执行链接格式 (ELF) 二进制文件的变体,操作系统加载程序时可以使用它们来组装运行进程。通常,我们通过 GCC 前端之一(gccg++gfortran 等)调用链接器:

链接程序:

gcc -o prog file.o ... -Ldir ... -lfoo ...

链接动态共享对象(DSO):

gcc -shared -o libbar.so file.o ... -Ldir ... -lfoo ...

当您链接其他程序或DSO时,共享库和静态库都可以通过统一的-lfoo协议提供给链接器。该选项指示链接器扫描其指定或默认搜索目录以查找libfoo.solibfoo.a。默认情况下,一旦找到其中之一,它将将该文件输入到链接中,如果在同一搜索目录中找到两者,则会首选libfoo.so。如果选择了libfoo.so,则链接器将将该DSO添加到正在制作的任何程序或DSO的运行时依赖关系列表中。如果选择libfoo.a,则链接器使用存档作为对象文件的选择,以便立即将它们链接到输出文件中(如果需要)。不可能在运行时依赖于libfoo.a本身;它无法映射到进程中,对操作系统加载程序没有意义。


9
我看过的最好的答案之一。感谢您分享您的知识。 - Royi
(1) "你的例子说明了一个欺骗。" - 在这种情况下,.a扩展名表示二进制文件是一个完整的、可直接使用的库文件,而不是一个上下文无关的对象文件。由于在Unix平台上,对于静态库来说,通常会带有.a扩展名,因此向拥有这种文件的用户表达这种方式并不是一个坏主意。对于最终的链接,技术上讲,它并不重要,所以在我看来,将其命名为相反的方式(使用.o)实际上是一种欺骗。 - bloody
这里有一篇关于静态库所谓动机的文章:在静态库中隐藏符号。但是不用担心,我认为链接器无法从这样合并的目标文件中进行有选择性的提取,这使得这种解决方案失效了 :) - bloody
一个很好的解释,我第一次使用“感谢”图标。再次感谢。 - Danijel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接