C ++链接是否足够聪明以避免链接未使用的库?

我完全不了解C ++链接器是如何工作的,而且我对它有一个具体的问题。

说我有以下几点:

Utils.h

namespace Utils { void func1(); void func2(); } 

Utils.cpp

 #include "some_huge_lib" // needed only by func2() namespace Utils { void func1() { /* do something */ } void func2() { /* make use of some functions defined in some_huge_lib */ } } 

main.cpp中

 int main() { Utils::func1() } 

我的目标是生成尽可能小的二进制文件。

我的问题是,将some_huge_lib将被包括在输出对象文件?

包括或链接大型图书馆通常不会有所作为,除非你使用的东西。 链接器应该执行死代码消除,从而确保在构build时,你将不会得到大量的二进制文件和大量的未使用的代码(阅读你的编译器/链接器手册了解更多,这不是由C ++标准强制执行)。

包括大量的头文件不会增加你的二进制大小(但它可能会大大增加你的编译时间,cfr预编译头文件)。 一些例外代表全局对象和dynamic库(不能被剥离)。 我也推荐阅读这篇文章 ( 仅限gcc ),将代码分成多个部分。

关于表演的最后一个通知:如果你使用了很多位置相关的代码(即不能映射到任何具有相对偏移量的地址,但需要通过重定位或类似表格进行一些“热补丁”)的代码,那么将会有启动成本。

这很大程度上取决于您使用哪些工具和开关来链接和编译。

首先,如果将some_huge_lib链接为共享库,则需要在链接共享库时parsing所有的代码和依赖关系。 所以是的,它会被拉到某个地方。

如果你把some_huge_lib作为一个档案,那么这取决于你。 为了读者的理智,将func1和func2放在单独的源代码文件中是一个很好的做法,在这种情况下,链接器一般可以忽略未使用的目标文件及其依赖关系。

但是,如果你在同一个文件中有两个函数,那么在某些编译器上,你需要告诉他们为每个函数生成单独的部分。 有些编译器会自动执行此操作,有些编译器根本就不这样做。 如果你没有这个选项,那么拉入func1会得到func2的所有代码,所有依赖关系都需要parsing。

将每个函数看作图中的一个节点。
每个节点都与一段二进制代码相关联 – 节点function的编译二进制代码。
如果一个节点(函数)依赖于(调用)另一个节点,则在两个节点之间存在链接(有向边)。

静态库主要是这样的节点的列表(+索引)。

程序起始节点main()函数。
链接器main()遍历graphics,并链接到可执行的所有可从main()获得的节点。 这就是为什么它被称为链接器 (链接映射可执行文件中的函数调用地址)。

未使用的函数,没有从main()发出的图中的节点的链接。
因此,这种断开的节点不可达,并且不包括在最终的可执行文件中。

可执行文件(而不是静态库)主要是从main() (其中包括索引和启动代码main()到达的所有节点的列表。

除了其他答复之外,必须说的是,连接器通常是按章节而不是按function工作的。

编译器通常具有可configuration性,无论它们是将所有的目标代码放入一个整体部分还是将其分割成若干个较小的部分。 例如,启用拆分的GCC选项是-ffunction-sections (用于代码)和-fdata-sections (用于数据); MSVC选项是/Gy (两者)。 -fnofunction-sections-fnodata-sections/Gy-分别将所有代码或数据放入一个部分。

您可以在两种模式下编译模块,然后转储它们( objdump for GCC, dumpbin for MSVC)来查看生成的目标文件结构。

一旦一段由编译器形成,对于链接器来说就是一个单元。 部分定义符号并引用在其他部分中定义的符号。 链接器将在部分之间build立依赖关系图(从一些根开始),然后解散或保留它们中的每一个。 因此,如果在某个部分中有一个已用和未使用的函数,则将保留未使用的函数。

这两种模式都有好处和缺点。 转向拆分意味着更小的可执行文件,但更大的目标文件和更长的链接时间。

还需要注意的是,在C ++中,与C不同的是,在某些情况下,放宽一个定义规则,允许多个函数或数据对象的定义(例如,内联函数)。 规则的制定方式允许链接器select任何定义。

从章节的angular度来看,将内联函数与非内联函数一起使用意味着,在典型的使用场景中,链接器通常会被迫实际上保留每个内联函数的每个定义; 这将意味着代码膨胀过度。 因此,不pipe编译器的命令行选项如何,这些函数和数据通常都放在它们自己的部分中。

更新:由于@janm在他的评论中正确提醒,链接器也必须通过指定--gc-sections (GNU)或/opt:ref (MS)来指示摆脱未引用的部分。