2.3. 将库与 GCC 搭配使用
了解在代码中使用库。
2.3.1. 库命名规则
用于库的一个特殊文件名称规则:例如称为 foo 的库一般会有文件 libfoo.so
或 libfoo.a
。通过 GCC 的链接输入选项(而非输出选项)自动理解此约定:
当对库进行链接时,库可以通过它的名称 foo 以及参数
-l
指定(-lfoo
):$ gcc ... -lfoo ...
-
在创建库时,必须指定完整文件名
libfoo.so
或libfoo.a
。
2.3.2. 静态和动态链接
开发人员可以选择使用完整编译语言构建应用时使用静态或动态链接。了解静态和动态链接之间的区别很重要,特别是在 Red Hat Enterprise Linux 上使用 C 和 C++ 语言的上下文中。总之,红帽不建议在 Red Hat Enterprise Linux 的应用程序中使用静态链接。
静态和动态链接的比较
静态链接使库成为生成的可执行文件的一部分。动态连接会将这些库保留为单独的文件。
可使用多种方式比较动态和静态链接:
- 资源使用
静态连接会导致包含更多代码的大型可执行文件。这个来自库的额外代码不能在系统上的多个程序间共享,在运行时增加文件系统的使用情况和内存使用情况。运行相同静态链接程序的多个进程仍将共享代码。
另一方面,静态应用程序需要较少的运行时重新定位,从而缩短启动时间,并需要较少的专用 RSS(resident set size)内存。因为使用独立于位置的代码(PIC)带来的开销,静态链路生成的代码比动态连接效率更高。
- 安全性
- 可动态链接库(提供 ABI 兼容性),无需更改可执行文件,具体取决于这些库。这对于由红帽提供的、作为 Red Hat Enterprise Linux 的一部分的库尤为重要,因为红帽会提供安全更新。强烈建议不要对任何此类库进行静态链接。
- 兼容性
静态连接似乎提供独立于操作系统提供的库版本的可执行文件。但是,大多数库依赖于其他库。使用静态链路时,这个依赖关系会变得不灵活,因此向前和向后兼容都会丢失。静态连接保证仅在构建可执行文件的系统中工作。
警告从 GNU C 库链接静态库(glibc)的应用程序仍然需要在系统中存在 glibc 作为动态库。另外,在应用程序运行时提供 glibc 的动态库变体必须是与应用程序链接时所用的相同版本。因此,静态连接可以保证仅在构建可执行文件的系统中工作。
- 支持覆盖范围
- 红帽提供的大多数静态库都在 CodeReady Linux Builder 频道中,它们不受红帽的支持。
- 功能
有些库,例如 GNU C 库(glibc)在静态链接时提供的功能会减少。
例如,当静态链接时,glibc 不支持线程以及在同一个程序中的任何形式的
dlopen()
调用。
因为存在这里列出的缺陷,应尽可能避免使用静态链接,特别是整个应用程序以及 glibc 和 libstdc++ 库。
静态链接的情况
在某些情况下,静态连接可能是合理的选择,例如:
- 使用没有为动态链接启用的库。
-
在空 chroot 环境或容器中运行代码需要完全静态链接。但是,红帽不支持使用
glibc-static
软件包的静态链接。
2.3.3. 链接时间优化
链接时间优化(LTO)可让编译器通过在链接时使用其中间表示,在程序的所有转换单元中执行各种优化。因此,您的可执行文件和库会较小并更快地运行。另外,您可以使用 LTO 在编译时分析软件包源代码,从而改进了潜在的编码错误的各种 GCC 诊断。
已知问题
违反一个定义规则(ODR)会产生
-Wodr
警告ODR 违规会导致未定义的行为产生一个
-Wodr
警告。这通常指向您的程序中的一个错误。默认启用-Wodr
警告。LTO 会导致内存消耗增加
当程序处理翻译单元由以下组成时,编译器会消耗更多内存。在内存有限的系统中,在构建程序时禁用 LTO 或者降低并行级别。
GCC 会删除以前未使用的功能
GCC 可能会删除它认为未使用的功能,因为编译器无法识别 asm() 语句引用哪些符号。因此,可能会出现编译错误。要防止这种情况,请在您的程序中使用的符号中添加
__attribute__((used))
。使用
-fPIC
选项编译会导致错误由于 GCC 不会解析 asm() 语句的内容,因此使用
-fPIC
命令行选项编译代码可能会导致错误。要防止这种情况,在编译您的翻译单元时使用-fno-lto
选项。
请参阅 LTO FAQ - Symbol usage from assembly 语言。使用
.symver
指令的符号进行版本与 LTO 不兼容在 asm()语句中使用
.symver
指令实现符号版本控制与 LTO 不兼容。不过,可以使用symver
属性实施符号版本控制。例如:__attribute__ ((_symver_ ("<symbol>@VERS_1"))) void <symbol>_v1 (void) { }
2.3.4. 将库与 GCC 搭配使用
库是可以在您的程序中重复使用的代码软件包。C 或 C++ 库由两个部分组成:
- 库代码
- 标头文件
使用库的编译代码
标头文件描述了库的接口:库中可用的函数和变量。编译代码需要来自标头文件的信息。
通常,库的标头文件将放在与应用程序代码不同的目录中。要告知 GCC 哪个标头文件,请使用 -I
选项:
$ gcc ... -Iinclude_path ...
使用到标头文件目录的实际路径替换 include_path。
可以多次使用 -I
选项来添加包含标头文件的多个目录。查找标头文件时,会按照它们在 -I
选项中的顺序搜索这些目录。
使用库的链接代码
在连接可执行文件时,应用程序的对象代码和库的二进制代码都必须可用。静态和动态库的代码有不同的形式:
-
静态库作为存档文件提供。它们包含一组对象文件。归档文件名为
.a
。 -
动态库作为共享对象提供。它们是可执行文件的一种形式。共享对象具有文件名扩展名
.so
。
要告知 GCC 一个库的归档或共享对象文件的位置,请使用 -L
选项:
$ gcc ... -Llibrary_path -lfoo ...
使用库目录的实际路径替换 library_path。
L
选项可以多次使用来添加多个目录。查找库时,会按照其 -L
选项顺序搜索这些目录。
选项顺序很重要:GCC 无法链接到库 foo,除非它知道这个库的目录。因此,在使用链接库的 -l
选项前,使用 -L
选项指定库目录。
在一个步骤中使用库编译和链接代码
当允许代码在一个 gcc
命令中编译和链接时,请一次性使用上述两个情况的选项。
2.3.5. 在 GCC 中使用静态库
静态库作为包含对象文件的存档提供。链接后,它们将成为生成的可执行文件的一部分。
出于安全原因,红帽不建议使用静态链接。请参阅 第 2.3.2 节 “静态和动态链接”。仅在必要时使用静态链接,特别是红帽提供的库。
先决条件
- 必须在您的系统中安装 GCC。
- 您必须了解静态和动态链接。
- 您有一组源或对象文件组成一个有效程序,需要一些静态库 foo 且没有其他库。
-
foo 库作为一个文件
libfoo.a
提供,且没有为动态链接提供文件libfoo.so
。
作为 Red Hat Enterprise Linux 一部分的大多数库都只支持动态链接。以下步骤只适用于 没有为 动态链接启用的库。
步骤
要从源和目标文件链接程序,添加一个静态链接的库 foo,它会是一个 libfoo.a
文件:
- 更改到包含您的代码的目录。
使用 foo 库的标头编译程序源文件:
$ gcc ... -Iheader_path -c ...
使用包含 foo 库的标头文件的目录路径替换 header_path。
将程序与 foo 库链接:
$ gcc ... -Llibrary_path -lfoo ...
使用包含文件
libfoo.a
的目录替换 library_path。要在以后运行该程序,只需:
$ ./program
与静态链路相关的 -static
GCC 选项,用于禁止所有动态链接。应该使用 -Wl,-Bstatic
和 -Wl,-Bdynamic
选项来更精确地控制链接器行为。请参阅 第 2.3.7 节 “在 GCC 中使用静态和动态库”。
2.3.6. 使用 GCC 的动态库
动态库可作为独立的可执行文件提供,在链接时间和运行时需要。它们独立于应用程序的可执行文件。
先决条件
- 必须在系统中安装 GCC。
- 一组源或对象文件组成一个有效程序,需要一些动态库 foo,没有其他库。
- foo 库必须作为一个文件 libfoo.so 可用。
将程序与动态库连接
要根据动态库 foo 链接程序:
$ gcc ... -Llibrary_path -lfoo ...
当某个程序与动态库相关联时,生成的程序必须始终加载库。查找库有两个选项:
-
使用存储在可执行文件本身中的
rpath
值 -
在运行时使用
LD_LIBRARY_PATH
变量
使用存储在 Executable 文件中的 rpath
值
rpath
是一个特殊值,保存在可执行文件时作为可执行文件的一部分保存。之后,当从可执行文件加载程序时,运行时链接器将使用 rpath
值来定位库文件。
当使用 GCC 链接时,将路径 library_path 存储为 rpath
:
$ gcc ... -Llibrary_path -lfoo -Wl,-rpath=library_path ...
路径 library_path 必须指向包含文件 libfoo.so 的目录。
不要在 -Wl,-rpath=
选项的逗号后添加一个空格。
稍后运行程序:
$ ./program
使用 LD_LIBRARY_PATH 环境变量
如果在程序的可执行文件中找不到 rpath
,则运行时链接器将使用 LD_LIBRARY_PATH
环境变量。必须为每个程序更改此变量的值。这个值应该代表共享库对象所在的路径。
要在没有设置 rpath
的情况下运行安装程序,库存在于路径 library_path 中:
$ export LD_LIBRARY_PATH=library_path:$LD_LIBRARY_PATH
$ ./program
不使用 rpath
值可提供一定的灵活性,但每次程序运行时都需要设置 LD_LIBRARY_PATH
变量。
将库放在默认目录中
运行时链路器配置将多个目录指定为动态库文件的默认位置。要使用这个默认行为,请将您的库复制到适当的目录中。
动态链路程序行为的完整描述超出了本文档的范围。如需更多信息,请参阅以下资源:
动态链路器的 Linux 手册页:
$ man ld.so
/etc/ld.so.conf
配置文件的内容:$ cat /etc/ld.so.conf
报告由动态链路器识别的库而无需额外的配置,其中包括目录:
$ ldconfig -v
2.3.7. 在 GCC 中使用静态和动态库
有时需要静态和一些库来动态链接一些库。这种情况给企业带来一些挑战。
先决条件
- 了解静态和动态链接
简介
GCC 同时识别动态和静态库。当遇到 -lfoo
选项时,gcc 将首先尝试查找包含 foo 库的动态链接版本的共享对象( .so
文件),然后查找包含该库静态版本的存档文件(.a
)。因此,以下情况可能会导致此搜索的结果:
- 仅找到共享对象,并动态 gcc 链接它。
- 只找到归档,并以静态方式使用 gcc 链接。
- 可以找到共享对象和存档,默认情况下,gcc 选择对共享对象的动态链接。
- 未找到共享对象或存档,并且链接失败。
由于这些规则,选择要链接库的静态或动态版本的最佳方法是,只有 gcc 找到的版本。在指定 -Lpath
选项时,可以通过使用或不使用包含库版本的目录来进行一定的控制。
另外,因为动态链接是默认设置的,所以当存在这两个版本的库时,需要明确指定链接的唯一情形是静态链接。有两种可能的解决方法:
-
通过文件路径而不是
-l
选项指定静态库 -
使用
-Wl
选项将选项传递给 linker
通过文件指定静态库
通常,gcc 指示针对带有 -lfoo
选项的 foo 库进行链接。但是,可以指定包含库的文件 libfoo.a
的完整路径:
$ gcc ... path/to/libfoo.a ...
通过文件扩展名 .a
,gcc 将了解到这是与程序链接的库。但是,指定库文件的完整路径是一个不太灵活的方法。
使用 -Wl
选项
gcc 选项 -Wl
是用于将选项传递给底层链路器的特殊选项。这个选项的语法与其他 gcc 选项不同。-Wl
选项后跟一个以逗号分隔的 linker 选项列表,而其他 gcc 选项则需要以空格分隔的选项列表。
gcc 使用的 ld linker 提供了选项 -Bstatic
和 -Bdynamic
以指定此选项后的库是否应静态链接或动态链接。将 -Bstatic
和库传递给 linker 后,必须手动恢复默认的动态链接行为,才能将以下库与 -Bdynamic
选项动态链接。
要链接程序,静态链接库 first(libfirst.a
),动态链接库 second(libsecond.so
):
$ gcc ... -Wl,-Bstatic -lfirst -Wl,-Bdynamic -lsecond ...
gcc 可以配置为使用默认 ld 以外的链接器。
其他资源
- 使用 GNU Compiler Collection (GCC) — 3.14 Options for Linking
- binutils 2.27 文档 - 2.1 命令行选项文档