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