Red Hat Training

A Red Hat training course is available for RHEL 8

2.2. 将库与 GCC 一起使用

了解在代码中使用库。

2.2.1. 库命名惯例

对库使用特殊文件名惯例:名为 foo 的库应该以文件 libfoo.solibfoo.a 的形式存在。通过链接 GCC 的输入选项(而非输出选项)可自动理解这一惯例:

  • 当链接到库时,只能将其名称 foo-l 作为 -lfoo 来指定库:

    $ gcc ... -lfoo ...
  • 在创建库时,必须指定完整文件名 libfoo.solibfoo.a

2.2.2. 静态和动态链接

开发人员在使用完全编译的语言构建应用程序时可以选择使用静态或动态链接。务必要了解静态和动态链接之间的区别,特别是在 Red Hat Enterprise Linux 上使用 C 和 C++ 语言的上下文中。总之,红帽不建议对 Red Hat Enterprise Linux 的应用程序使用静态链接。

静态和动态链接的比较

静态链接使库成为生成的可执行文件的一部分。动态链接将这些库保留为单独的文件。

可以通过多种方式比较动态和静态链接:

资源使用

静态链接会导致更大的可执行文件,其中包含更多代码。这些额外的代码来自不能在系统上的多个程序之间共享的库,这会增加运行时文件系统的使用率和内存的使用率。运行同一静态链接的程序的多个进程仍将共享代码。

另一方面,静态应用需要较少的运行时重定位,从而减少启动时间,并且需要较少的专用常驻集大小(RSS)内存。由于位置无关代码(PIC)引入的开销,为静态链接生成的代码比动态链接更高效。

安全性
可以更新提供 ABI 兼容的动态链接库,而无需根据这些库更改可执行文件。这对于由红帽提供的、作为 Red Hat Enterprise Linux 的一部分的库来说尤为重要,红帽提供安全更新。强烈反对对任何此类库的静态链接。
兼容性

静态链接似乎提供独立于操作系统提供的库版本的可执行文件。但是,大多数库依赖于其他库。有了静态链接,此依赖变得不灵活,因此会丢失向前和向后兼容性。静态链接可保证仅在构建可执行文件的系统上工作。

警告

静态链接 GNU C 库的应用程序(glibc)仍然需要 glibc 作为动态库存在于系统中。此外,在应用程序的运行时可用的 glibc 的动态库变体在连接应用程序时必须与当前版本按位相同。因此,静态链接保证仅在构建可执行文件的系统上工作。

支持范围
红帽提供的大多数静态库都位于 CodeReady Linux Builder 通道中,不受红帽的支持。
功能

某些库,特别是 GNU C 库(glibc),在静态链接时提供减少的功能。

例如,当静态链接时,glibc 不支持线程和在同一程序中对 dlopen() 函数的任何形式的调用。

由于所列出的缺点,应该不惜一切代价避免静态链接,特别是对于整个应用程序以及 glibclibstdc++ 库。

静态链接的情况

在某些情况下静态链接可能是一种合理的选择,例如:

  • 使用未对动态链接启用的库。
  • 对于在空的 chroot 环境或容器中运行的代码,需要完全静态链接。但是,红帽不支持使用 glibc-static 软件包的静态链接。

2.2.3. 将一个库与 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.2.4. 将一个静态库与 GCC 一起使用

静态库作为包含目标文件的存档提供。链接后,它们成为生成的可执行文件的一部分。

注意

出于安全原因,红帽不建议使用静态链接。请参阅 第 2.2.2 节 “静态和动态链接”。仅在需要时才使用静态链接,特别是对红帽提供的库。

先决条件

注意

作为 Red Hat Enterprise Linux 一部分的大多数库都只支持动态链接。以下步骤仅适用于 没有 为动态链接启用的库。请参阅 第 2.2.2 节 “静态和动态链接”

流程

要从源和目标文件链接程序,请添加静态链接库 foo,该库可作为 libfoo.a 文件找到:

  1. 进到包含您代码的目录。
  2. 编译带有 foo 库的头的程序源文件:

    $ gcc ... -Iheader_path -c ...

    使用包含 foo 库的头文件的目录的路径替换 header_path

  3. 将程序与 foo 库链接:

    $ gcc ... -Llibrary_path -lfoo ...

    使用包含文件 libfoo.a 的目录的路径替换 library_path

  4. 要稍后运行该程序,只需:

    $ ./program
警告

与静态链接有关的 -static GCC 选项禁止所有动态链接。相反,请使用 -Wl、-Bstatic-Wl,-Bdynamic 选项更精确地控制链接器行为。请参阅 第 2.2.6 节 “将静态库和动态库与 GCC 一起使用”

2.2.5. 将一个动态库与 GCC 一起使用

动态库作为独立的可执行文件提供,在链接时和运行时需要。它们独立于您应用程序的可执行文件。

先决条件

  • GCC 必须安装在系统上。
  • 组成有效程序的一组源或目标文件需要一些动态库 foo ,但不需要其他库。
  • foo 库必须作为文件 libfoo.so 提供。

将程序与动态库链接

要将程序与动态库 foo 链接:

$ gcc ... -Llibrary_path -lfoo ...

当程序链接了动态库时,生成的程序必须总是在运行时加载库。定位库有两个选项:

  • 使用存储在可执行文件本身中的 rpath
  • 在运行时使用 LD_LIBRARY_PATH 变量

使用存储在可执行文件中的 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.2.6. 将静态库和动态库与 GCC 一起使用

有时需要静态链接一些库,有时需要动态链接一些库。这种情况带来了一些挑战。

简介

gcc 识别动态和静态库。遇到 -lfoo 选项时,gcc 将首先尝试查找包含 foo 库的动态链接版本的共享目标(一个 .so 文件),然后查找包含库的静态版本的存档文件(.a)。因此,这个搜索可能会导致以下情况:

  • 只找到了共享目标,gcc 会动态链接它。
  • 只找到了归档,gcc 会静态链接它。
  • 共享目标和存档都都找到了,默认情况下,gcc 会选择对共享目标的动态链接。
  • 共享目标和存档都未找到,链接失败。

由于这些规则,选择用于链接的库的静态或动态版本的最佳方法是让 gcc 只找到该版本。在指定 -Lpath 选项时,可以使用或省略包含库版本的目录,来在某种程度上控制它。

此外,由于动态链接是默认的,因此链接被明确指定的唯一情形是存在两个版本的库都应被静态链接时。有两种可能的解决方案:

  • 通过文件路径而不是 -l 选项指定静态库
  • 使用 -Wl 选项将选项传给链接器

通过文件指定静态库

通常,使用 -lfoo 选项指示 gcc 链接到 foo 库。但是,可以指定包含库的 libfoo.a 文件的全路径:

$ gcc ... path/to/libfoo.a ...

从文件扩展名 .agcc 将理解为这是一个与程序链接的库。但是,指定库文件的全整路径是一个不太灵活的方法。

使用 -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 可以配置为使用默认 ld 以外的链接器。

其他资源