假设库A有一个()和b()。如果我将程序B与A链接并调用a(),b()是否包含在二进制文件中?编译器是否看到程序中的任何函数调用b()(可能a()调用b()或另一个lib调用b())?如果是这样,编译器如何获取此信息?如果没有,如果我链接到一个大型库但只使用次要功能,这不是对最终编译大小的大浪费吗?
答案 0 :(得分:9)
看看link-time optimization。这必然取决于供应商。它还取决于您如何构建二进制文件。 MS编译器(至少2005年开始)提供了一种称为Function Level Linking的东西 - 这是剥离你不需要的符号的另一种方式。 This帖子解释了如何通过海湾合作委员会实现同样的目标(这是旧的,GCC必须继续前进,但内容与您的问题相关)。
另请参阅LLVM实现(以及示例部分)。
我建议你也看看John Levine的Linkers and Loaders - 一本很好的读物。
答案 1 :(得分:8)
取决于。
如果库是共享对象或DLL,则会在运行时加载库中的所有内容。额外内存的成本(希望)通过在使用该库的内存中的所有进程之间共享库(实际上是代码页)来抵消。对libc
这样的事情来说,这是一个很大的胜利。所以,对于myreallyobscurelibrary.so
来说,情况就不那么好了。但是你可能并不是在询问共享对象。
静态库只是单个目标文件的集合,每个目标文件都是单独编译(或汇编)的结果,甚至可能不用相同的源语言编写。每个目标文件都有许多导出的符号,并且几乎总是有许多导入的符号。
链接器的工作是创建一个没有剩余未定义导入符号的已完成可执行文件。 (当然,我在撒谎,如果允许动态链接,但请耐心等待。)为此,它首先在链接命令行上显式命名的模块(可能隐含在其配置中),并假设任何模块明确命名必须是完成的可执行文件的一部分。然后,它尝试查找所有未定义符号的定义。
通常,命名对象模块希望从某些库中获取符号,例如libc.a
。
在您的示例中,您有一个调用函数a()
的模块,这将导致链接器查找导出a()
的模块。
你说名为A的库(在unix上,可能是libA.a
)提供a()
和b()
,但是你没有指定。你暗示a()
和b()
不会互相打电话,我会假设。
如果libA.a
是从a.o
和b.o
构建的,其中每个都定义了相应的单个函数,则链接器将包含a.o
并忽略b.o
。< / p>
但是,如果libA.a
包括定义ab.o
和a()
的{{1}},那么它将在链接中包含b()
,满足ab.o
的需要1}},包括未使用的函数a()
。
正如其他人所提到的,有些链接器能够将各个功能从模块中分离出来,并且只包括实际使用的那些功能。在许多情况下,这是安全的事情。但是,除非您有特定的文档,否则通常最安全的做法是假设您的链接器不会这样做。
需要注意的是,大多数链接器通过命令行上命名的文件和库可以进行尽可能少的传递,并在它们运行时构建它们的符号表。实际上,这意味着最好在链接命令行上的所有对象模块之后始终指定库。
答案 2 :(得分:2)
通常(静态)库由从源文件创建的对象组成。如果引用该对象提供的函数,则链接器通常会包含该对象。如果源文件只包含一个函数,那么只有链接器才会引入该函数。那里有更复杂的连接器,但大多数基于C的连接器仍然像概述的那样工作。有些工具可以将包含多个函数的C源拆分为人工较小的源文件,以使静态链接更精细。
如果您使用的是共享库,则不会通过使用更多或更少的共享库来影响编译的大小。但是,您的运行时大小将包含它们。
答案 3 :(得分:1)
这取决于链接器。
例如。 Microsoft Visual C ++有一个选项“启用功能级别链接”,因此您可以手动启用它。
(我认为他们有理由不只是一直启用它......可能链接速度较慢或者其他东西)
答案 4 :(得分:1)
本地球学院的这个lecture提供了一个非常好的概述,在谈话的后半部分IIRC附近讨论了链接。
答案 5 :(得分:0)
它取决于链接器,但通常只有实际调用的函数才包含在最终的可执行文件中。链接器的工作原理是查找库中的函数名称,然后使用与名称相关联的代码。
很少有关于连接子的书籍,当你认为它们有多重要时,这很奇怪。可以找到一个好的文本here。
答案 6 :(得分:0)
没有任何优化,是的,它将被包括在内。但是,链接器可能能够通过静态分析代码并尝试删除无法访问的代码来优化。
答案 7 :(得分:0)
这取决于传递给链接器的选项,但通常链接器会遗漏库中未在任何地方引用的目标文件。
$ 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
答案 8 :(得分:0)
这取决于链接器以及库的构建方式。通常库是目标文件的组合(导入库是一个主要的例外)。较旧的链接器会以放入库中的目标文件的粒度将内容放入输出文件映像中。因此,如果函数a()
和函数b()
都在同一个目标文件中,它们都将在输出文件中 - 即使实际只引用了两个函数中的一个。
这就是为什么你经常会看到每个源文件都有一个C函数策略的面向库的项目的原因。这样,每个函数都打包在自己的目标文件中,并且链接器只引入引用的内容没有问题。
但请注意,较新的链接器(当然是更新的Microsoft链接器)只能引入被引用的部分目标文件,因此现在不太需要强制实施每个源代码的单一功能文件策略 - 尽管有合理的论据认为无论如何都应该做到可维护性。