链接器如何决定要包含哪些库?

假设库A有a()和b()。 如果我将程序B与A链接并调用a(),b()是否包含在二进制文件中? 编译器是否看到程序中的任何函数调用b()(可能a()调用b()或另一个lib调用b())? 如果是这样,编译器如何获取此信息? 如果没有,如果我链接到一个大型库但只使用次要function,这不是对最终编译大小的大浪费吗?

看看链接时优化 。 这必然取决于供应商。 它还取决于您如何构建二进制文件。 MS编译器(至少2005年起)提供了一种称为function级链接的东西 – 这是剥离你不需要的符号的另一种方式。 这篇文章解释了如何通过GCC实现同样的目标(这是旧的,GCC必须继续前进,但内容与您的问题相关)。

另请参阅LLVM实现(以及示例部分)。

我建议你也看看John Levine的Linkers and Loaders–一本很好的读物。

这取决于。

如果库是共享对象或DLL,则会在运行时加载库中的所有内容。 额外内存的成本(希望)通过在使用该库的内存中的所有进程之间共享库(实际上是代码页)来抵消。 对于像libc .so这样的东西,这是一个很大的胜利,对于myreallyobscurelibrary.so则不那么myreallyobscurelibrary.so 。 但是你可能并不是在询问共享对象。

静态库只是单个目标文件的集合,每个目标文件都是单独编译(或汇编)的结果,甚至可能不用相同的源语言编写。 每个目标文件都有许多导出的符号,并且几乎总是有许多导入的符号。

链接器的工作是创建一个没有剩余未定义导入符号的已完成可执行文件。 (当然,我在撒谎,如果允许动态链接,但请耐心等待。)为此,它首先在链接命令行上显式命名的模块(可能隐含在其配置中)并假设任何模块明确命名必须是完成的可执行文件的一部分。 然后,它尝试查找所有未定义符号的定义。

通常,命名对象模块期望从某些库获取符号,例如libc.a

在您的示例中,您有一个调用函数a()模块,这将导致链接器查找导出a()模块。

你说名为A的库(在unix上,可能是libA.a )提供a()b() ,但你没有指定如何。 你暗示a()b()不会互相调用,我将假设。

如果libA.a是从aobo构建的,其中每个都定义了相应的单个函数,那么链接器将包含ao并忽略bo

但是,如果libA.a包含定义a()b() ab.o ,那么它将在链接中包含ab.o ,满足a()的需要,并包括未使用的函数b()

正如其他人所提到的,有些连接器能够将各个function从模块中分离出来,并且只包括那些实际使用的连接器。 在许多情况下,这是一件安全的事情。 但是,除非您有特定的文档,否则通常最安全的做法是假设您的链接器不会这样做。

需要注意的是,大多数链接器通过在命令行上命名的文件和库来进行尽可能少的传递,并在它们运行时构建它们的符号表。 实际上,这意味着在链接命令行上的所有对象模块之后始终指定库是一种好习惯。

通常(静态)库由从源文件创建的对象组成。 如果引用该对象提供的函数,则链接器通常会包含该对象。 如果源文件只包含一个函数,那么只有链接器才能引入该函数。 有更复杂的连接器,但大多数基于C的连接器仍然像概述的那样工作。 有一些工具可以将包含多个函数的C源拆分为人工较小的源文件,以使静态链接更精细。

如果您使用的是共享库,则不会通过使用更多或更少的共享库来影响编译的大小。 但是,您的运行时大小将包含它们。

这取决于链接器。

例如。 Microsoft Visual C ++有一个选项“启用function级别链接”,因此您可以手动启用它。

(我认为他们有理由不只是一直启用它……可能链接速度较慢或者某些东西)

这个讲座在学术地球上提供了一个非常好的概述,在谈话的后半部分IIRC附近谈论了联系。

没有任何优化,是的,它将被包括在内。 但是,链接器可能能够通过静态分析代码并尝试删除无法访问的代码来优化。

它取决于链接器,但通常只有实际调用的函数才包含在最终的可执行文件中。 链接器的工作方式是查找库中的函数名称,然后使用与名称关联的代码。

关于连接子的书很少,当你认为它们有多重要时,这很奇怪。 一个好的文本可以在这里找到。

它取决于传递给链接器的选项,但通常链接器会遗漏库中未在任何地方引用的目标文件。

 $ cat foo.c int main(){} $ gcc -static foo.c $ size text data bss dec hex filename 452659 1928 6880 461467 70a9b a.out # force linking of libz.a even though it isn't used $ gcc -static foo.c -Wl,-whole-archive -lz -Wl,-no-whole-archive $ size text data bss dec hex filename 517951 2180 6844 526975 80a7f a.out 

这取决于链接器以及库的构建方式。 通常库是目标文件的组合(导入库是一个主要的例外)。 较旧的链接器会以放入库中的目标文件的粒度将内容放入输出文件映像中。 因此,如果函数a()和函数b()都在同一个目标文件中,它们都将在输出文件中 – 即使实际只引用了两个函数中的一个。

这就是为什么您经常会看到每个源文件具有单个C函数策略的面向库的项目的原因。 这样,每个函数都打包在它自己的目标文件中,并且链接器没有问题只引入所引用的内容。

但请注意,较新的链接器(当然是更新的Microsoft链接器)只能引入被引用的目标文件的一部分,因此现在不太需要强制实施每个源的单一function文件策略 – 尽管有合理的论据,无论如何都应该为可维护性而做。