C++ 静态库链接

2024年8月28日 | 阅读 4 分钟

引言

在C++中,静态库是一组已合并到单个文件中的目标文件,可以在编译时链接到程序中。当程序链接到静态库时,该静态库中声明的所有变量和函数都会被包含到生成的可执行文件中。在本文中,我将详细解释C++中静态库链接的工作原理。

静态库:创建

在将静态库链接到程序之前,必须先构建它。要创建静态库,我们必须首先编译我们打算包含在库中的每个源文件。然后,使用“ar 命令”从目标文件生成一个归档文件。在类Unix系统上,归档文件的扩展名是“.a”,而在Windows系统上是“.lib”。

假设我们要将两个源文件 foo.cpp 和 bar.cpp 包含在一个名为 libfoobar.a 的静态库中。首先,我们会将每个源文件编译成目标文件:

然后,可以使用“ar”命令创建归档文件:

这样,目标文件 foo.o 和 bar.o 就被合并成一个名为 libfoobar.a 的归档文件。

静态库链接

在我们将程序与静态库链接之前,必须向编译器指定静态库的位置。这可以通过使用 -L 选项,后跟库所在的目录来完成。此外,还必须使用 -l 选项指定库的名称,但不需要 lib 前缀或 .a 扩展名。

例如,假设一个名为 main.cpp 的程序使用了 libfoobar.a 库中的函数。该程序将按如下方式编译和链接:

这会指示编译器在当前目录中搜索(-L.)并与 libfoobar.a 库(-lfoobar)进行链接。

当程序与静态库链接时,静态库中声明的所有函数和变量都会被包含在最终的可执行文件中。这意味着如果库很大,可执行文件的大小可能会显著增加。

符号引用

在将程序与静态库链接时,链接器必须解析对静态库中声明的任何符号的引用。这是通过在库的目标文件中查找符号定义来完成的。

如果符号有定义,链接器会将其包含在程序中,并修改符号表以指向该定义。如果链接器找不到符号的定义,它会报告一个未定义符号的错误。

例如,假设我们程序的 main 函数调用了在 libfoobar.a 库中定义的函数 foo。当我们将应用程序与该库链接时,链接器会在库的目标文件中查找 foo 的定义。如果找到了,它会将定义添加到程序中,并修改符号表以包含对该定义的引用。如果找不到定义,则会报告未定义符号的错误。

链接顺序

在链接器命令行上指定目标文件和库的顺序可能会影响链接过程。通常,目标文件和库应按依赖关系的升序指定。

假设程序 main.cpp 调用了 libfoo.a 和 libbar.a 两个库。如果 libfoo.a 库使用了 libbar.a 库中描述的符号,我们必须按正确的顺序指定库:

在这个例子中,即使 main.cpp 调用了两个库中的函数,libfoo.a 库也在 libbar.a 库之前被提及。因为 libfoo.a 依赖于 libbar.a 中定义的符号,所以必须先链接 libbar.a,以确保在使用这些符号之前它们已经被定义。

链接时优化

一些链接器和编译器支持链接时优化(LTO)。LTO在链接阶段对目标文件和库进行优化,这可以带来更好的性能和更小的代码体积。

要启用 LTO,我们必须首先使用 -flto 选项编译源文件并启用 LTO:

然后,我们可以使用 -flto 选项链接程序并启用 LTO:

LTO可以显著提升代码的性能,但它也可能增加链接时间和内存使用量。此外,并非所有的链接器和编译器都支持LTO。

结论

在本文中,我们讨论了C++中静态库链接的工作原理。我们已经了解了如何构建静态库、如何将程序与之链接、链接器如何处理符号引用,以及目标文件和库的排列顺序如何影响链接。我们还讨论了链接时优化,它可以使我们的程序运行得更快。


下一主题C++中的Constexpr