Menu Close

Red Hat Training

A Red Hat training course is available for RHEL 8

第 3 章 在 GCC 中使用代理

本章论述了在代码中使用库。

3.1. 库命名惯例

库使用一个特殊的文件名惯例: 称为 foo 的库应该 作为文件 libfoo.solibfoo.a 存在。本约定通过链接 GCC 的输入选项来自动理解,而不是由输出选项来理解:

  • 当与库连接时,该库只能使用名称 foo 指定,其 -l 选项为 -lfoo:

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

3.2. 静态和动态链接

在使用完全编译的语言构建应用程序时,开发人员可以选择使用静态或动态链接。本节列出了不同之处,特别是使用 Red Hat Enterprise Linux 中的 C 和 C++ 语言。要总结,红帽不建议在 Red Hat Enterprise Linux 的应用程序中使用静态链接。

静态和动态链接的比较

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

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

资源使用

静态链接会产生更大的可执行文件,其中包含更多代码。这个来自库的额外代码无法在系统的多个程序间共享,从而增加文件系统的使用量和内存用量。运行相同静态链接程序的多个进程仍可共享代码。

另一方面,静态应用程序需要较少的运行时重新定位,从而减少启动时间,并需要较少的私有设置大小(RSS)内存。由于位置独立代码(PIC)引入的开销,静态链接生成的代码比动态链接效率更高。

安全性
可更新提供 ABI 兼容性的动态链接库,而无需根据这些库更改可执行文件。这对于红帽作为 Red Hat Enterprise Linux 的一部分提供的库尤其重要,红帽在其中提供了安全更新。强烈建议您对任何这样的库进行静态连接。
兼容性

静态链接可能提供独立于操作系统提供的库版本的可执行文件。但是,大多数库依赖于其他库。使用静态连接时,这个依赖关系变得不灵活,因此前向兼容性和向后兼容性都会丢失。静态链接保证只能用于构建可执行文件的系统。

警告

从 GNU C 库(glibc)连接静态库的应用程序仍需要 glibc 作为动态库存在于系统中。另外,应用程序运行时可用的 glibc 动态库变体在连接应用程序时必须是一个与存在的 glibc 相同的版本。因此,可以保证静态链接只适用于构建可执行文件的系统。

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

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

例如,当静态链接时, glibc 不支持线程和同一程序中对 dlopen() 功能的调用。

由于列出了缺陷,应该避免以所有成本为单位进行静态连接,特别是针对整个应用程序以及 glibclibstdc++ 库

静态链接的情况

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

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

其它资源

3.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 命令中编译并链接代码时,对上述两种情况都使用选项。

其它资源

3.4. 在 GCC 中使用静态库

静态库可作为包含对象文件的归档使用。连接后,它们就成为生成的可执行文件的一部分。

注意

出于安全考虑,红帽不建议使用静态链接。请查看 第 3.2 节 “静态和动态链接”。只在需要时才使用静态链接,特别是红帽提供的库。

先决条件

注意

大多数作为 Red Hat Enterprise Linux 一部分的库只支持动态链接。以下步骤只适用于 没有 启用动态链接的库。请查看 第 3.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 选项更精确地控制链路器行为。请查看 第 3.6 节 “通过 GCC 使用静态和动态库”

3.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

3.6. 通过 GCC 使用静态和动态库

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

简介

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

  • 只找到共享对象,并动态 使用 gcc 链接。
  • 只会找到存档,静态找到针对它的 gcc 链接。
  • 找到共享对象和存档,默认情况下, gcc 选择针对共享对象的动态链接。
  • 找不到共享对象或存档,链接会失败。

由于这些规则,选择用于链接的静态或动态版本库的最佳方法是只有 gcc 找到的版本。当指定 -Lpath 选项时,这可以通过使用或退出包含库版本的目录来控制。

另外,由于动态链接是默认的,所以必须明确指定连接的唯一情况是,具有两个版本的库应该静态链接。有两个可能的解决方案:

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

通过文件指定静态库

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

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

从文件扩展 .a 中, gcc 会了解到这是一个与程序链接的库。但是,指定到库文件的完整路径并不灵活。

使用 -Wl 选项

gcc 选项 -Wl 是将选项传递给底层链接器的特殊选项。这个选项的语法与其他 gcc 选项不同。-Wl 选项后跟一个用逗号分开的链接选项列表,其他 gcc 选项则需要用空格分开的选项列表。

gcc 使用的 ld 链接程序 提供选项 -Bstatic-Bdynamic 来分别指定这个选项后面的库是静态链接还是动态链接。在将 -Bstatic 和库传递给链接器后,必须手动恢复默认的动态链接行为,才能让以下库与 -Bdynamic 选项动态链接。

要连接程序库, 首先 静态链接库(libfirst.a), 第二个 动态链接(libsecond.so):

$ gcc ... -Wl,-Bstatic -lfirst -Wl,-Bdynamic -lsecond ...
注意

GCC 可以配置为使用默认 ld 以外的链接。

其它资源