📂系统:库与二进制

type
status
date
slug
summary
tags
category
icon
password
本篇以 gcc 编译器为例,讨论 Linux 中库、编译和二进制的关系,介绍了静态、动态库的区别与使用。

1. 库

1.1. 库的概念

库:实现了某一类功能的若干函数和二进制代码的集合。库的后缀名在不同平台上表现不同: Windows:静态库:xxx.lib && 动态库:xxx.dll Linux:静态库:xxx.a && 动态库:xxx.so

1.2. 库的分类

库分为静态库和动态库,与之对应的操作是静态链接和动态链接,静态库不能采用动态链接,同理,动态库也不能采用静态链接。 静态库:在链接的时候,函数库被完整的拷贝到可执行文件中,对应的链接方式成为静态链接,采用 gcc -static 指令。这里的可执行文件可以使单纯的一个 a.out,也可以是一整个 app 或者程序。 动态库:相对于静态库,动态库并没有将库中所有的数据复制到可执行文件中,在程序运行的时候才会被动态载入。

2. 区别和特点

2.1. 静态库

  • 一旦链接完成,执行程序就和函数库没有任何关联;
  • 占用空间和资源,拷贝多次就会占用多份资源;
  • 会导致升级不便,一个地方修改就需要全量更新;

2.2. 动态库

  • 可以实现进程之间的资源共享;
  • 动态库把对一些库函数的链接载入推迟到程序运行的时期;
  • 将一些程序升级变得简单;
  • 可以通过显示调用做到链接载入完全由程序员在程序代码中控制;
  • 动态库的创建直接使用编译器即可创建动态库,不需要打包工具;

3. 示例演示

这里试用 gcc 编译器 + C 语言作为演示,其他编译器配合 C/C++ 类似。

3.1. 编译过程简介

gcc 编译器在 c 语言到 可执行文件中,主要包含四步:
  • 预处理 Pre-Processing:aaa.c -> aaa.i
  • 编译 Compiling:aaa.i -> aaa.s
  • 汇编 Assembling:aaa.s -> aaa.o
  • 链接 Linking:bbb.so/ccc.a + aaa.o -> aaa
动静态库区别,主要在第四步链接的方式不同。
notion image

3.2. 原理演示

文件 mymath.h
文件 mymath.c
文件 main.c
测试脚本:

3.3. 输出与分析

可以看到,动态链接生成的二进制 dynamic-main 文件,在运行时依赖 libmymath.so,这里 not found 是因为没有把 libmymath.so 放到系统索引目录里,只在编译时用了 -L. 参数;静态编译不依赖任何 so。

4. 概念补充

4.1. ELF

4.1.1. ELF 介绍

ELF,全称 executable and linkeable file,是一种对可执行文件、目标文件和库使用的文件格式,跟 Windows 下的 PE 文件格式类似。ELF 格式是 Unix 系统实验室作为 ABI 而开发和发布的。
ELF 主要包括三类文件:
  • 可重定位文件:编译生成的 .o 文件;
  • 可执行文件:Linker 对 .o 处理进行输出的文件;
  • 共享对象文件:动态库文件。

4.1.2. ELF 布局

ELF 布局如下:
notion image
  • ELF header:描述体系结构和操作系统的基本信息,指出 section header table 和 program header table;
  • program header table:给出了各个 segment 的信息,从运行角度来看 ELF 文件;
  • section header table:给出了各个 section 的信息,从编译和链接的角度来看 ELF 文件;
  • sections:各个节区;
  • segments:各个段区;
可以注意到 sections 和segments 占用的地方是一样的,左边的视图是从编译和链接的角度看,右边则是从运行的角度看。

4.1.3. ELF 解读

在 Linux 下,一般使用 readelf 工具读取 elf 文件:

4.2. ldd

ldd 是 List Dynamic Dependencies 的缩写,它列出了应用程序所需的所有共享对象。它通过设置特殊的环境变量来调用动态链接器。使用二进制文件的位置运行 ldd 时,它会返回一个输出,其中包含依赖关系的列表、它们的位置以及表示它们加载到内存的十六进制值。

5. 混合编译

5.1. 举例

假设目前有一个 test.c 文件,它的编译依赖静态库头文件 include/headerb.h,动态库 /usr/lib/libdynamica.so,lib/libdynamicb.so,lib/libstatica.a 等。
  • 第一行:编译与输出文件名;
  • 第二行:链接系统动态库 dynamica;
  • 第三行:链接本地动态库 dynamicb;
  • 第四行:链接本地头文件 include,链接本地静态库 statica

5.2. 说明

  • gcc 编译器中,-I 代表头文件路径,-L 代表库路径,-l 代表库名称,静动态库相同;
  • gcc 编译器中,一旦编译选项中出现 -static,后面的所有选项自动转为静态库;

6. 参考

中年码农的困境log-001-我与计算机