学习导航
- 一、关于动静态库的基本认识
- 二、设计库的工程师角度
- (1)制作静态库
- (2)制作动态库
- 二、使用库的用户角度
- (1)使用静态库
- (2)使用动态库
- 三、理解的角度
一、关于动静态库的基本认识
1.静态库
- 静态库以
.a
作为文件后缀 - 程序在编译链接的时候,将静态库的代码拷贝到可执行文件中,因此程序运行的时候将不再依赖库文件
2.动态库
- 动态库以
.so
作为文件后缀 - 程序在运行的时候才去链接动态库的代码,多个程序间共享同一份库代码
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间 。因此动态库的使用更加广泛
二、设计库的工程师角度
(1)制作静态库
制作静态库有以下三个步骤:
-
将所有的
.c
文件编译生成.o
文件。
-
打包成静态库
我们只提供所有的 .o 文件,别人可以直接链接使用吗?可以。因为链接的本质就将需要的 .o 文件链接起来。我们可以手动将所有.o
文件链接:
虽然我们可以直接将所有.o
文件链接生成可执行程序,但是当库中的.o文件很多的时候,使用会比较繁琐,整个文件也会比较冗余。因此我们可以使用ar
(archive) 指令将所有.o文件打包成静态库:
[注意]:库的名字也是有讲究的,需要以 lib 作为前缀,.a 或者 .so作为后缀 -
发布
使用一个库需要什么?头文件
和库文件
,因此我们分别将其添加到 /lib文件夹和/include文件夹下,作为我们的发布方式:
(2)制作动态库
- 将所有的
.c
文件编译生成.o
文件
注意要带上-fPIC
选项,从而产生位置无关码,采用相对地址的方案确定动态库的位置
- 生成动态库
- shared
选项用于指定生成共享库 - 发布
二、使用库的用户角度
大家都知道,包含头文件有以下两种方式:
- include “xxx” :在当前目录下寻找头文件
- include :在系统目录下寻找头文件
(1)使用静态库
在使用自实现的库时,包含头文件是一个大问题。因为往往我们所写的目标文件和库文件、头文件不在同一路径下,而且库文件和头文件通常会分别存储(参见发布方式),因此我们不能直接使用上述的两种包含头文件的方式。以下给出三种解决方案:
- 拷贝到系统路径下
系统的头文件被存放在/usr/include
路径下,库文件则被存放在/lib64
路径下 。编译器会自动去系统路径下寻找头文件和库文件,因此我们可以考虑将头文件和库文件添加到系统路径中。这本质就是在安装第三方库。
但是当我们再次尝试使用库文件和头文件时,发生了如下的错误:
我们要搞清楚这样一个概念:路径只是用来找到库在哪里,但是某个路径下可能同时存在很多的库。当我们使用第三方库(非C语言标准)时,编译器原生是不认识的,所以我们需要在编译时主动添加-l
选项指定我们要链接的库:
【说明】:
-l
和后面的库名之间,空格也有可五-l
指定的库名需要去掉lib
前缀和.a
后缀,因为在匹配的时候会自动加上。例如使用名为libmydate.a
的库文件时,就需要写成-lmydate
但是不推系荐将我们日常写的库添加到系统路径下,因为有可能会污染系统目录的命名池。当然如果你水平很高,那也当然没问题。
- 指定搜索路径
[说明]:
- -I选项:指定头文件查找的路径
- -L选项:指定库文件查找的路径
- -l选项:指定使用的库
(2)使用动态库
当我们指定文件路径生成可执行程序后,尝试运时却遇到如下错误提示:
使用ldd
指令查看文件的依赖关系就可以发现问题所在:
为什么 .so
文件会找不到呢,我们明明指定了路径了啊?这里我们又需要区分一组概念了:
-I
-L
-l
都是 gcc 的选项,只与gcc有关- 形成可执行程序后,gcc就与之后的事无关了
- 程序是由进程运行的。没有人告诉进程库文件所在路径,自然会出现上述的错误提示
下面提供四种解决方案:
- 将库文件添加到系统路径下
程序运行时会自动去/lib64
路径下查找对应库文件 - 将库文件导入到环境变量中
程序运行时,会自动在LD_LIBRARY_PATH
环境变量中查找需要的动态库路径,因此我们可以手动将库文件的路径添加到LD_LIBRARY_PATH
中,最好添加绝对路径,这样在哪里都可以用。(:用于分隔不同路径)
// 注意不要写成如下的形式,这样就将LD_LIBRARY_PATH的初始数据覆盖了
export LD_LIBRARY_PATH=xxx
-
配置系统文件
当我们重新启动xshell的时候,会发现我们之前追加的环境变量信息不见了。这是因为每次重启的时候,bash都会从配置文件中读取数据重新生成环境变量。
为了永久修改,我们可以尝试修改系统配置文件。操作系统除了在默认路径下搜索库文件外,还会遍历/etc/ld.so.conf.d
下的所有配置文件,根据配置文件的内容找到目标库所在路径。配置文件的内容也非常简单,就是库文件所在的路径
再使用
ldconfig
命令将配置文件加载到内存中,从而让配置文件生效。
-
在系统路径中创建库文件的软链接
软链接本质上就是快捷方式,使用unlink
指令可以取消链接
三、理解的角度
[问题一]:为什么静态库不需要运行时查找?
答:使用静态库生成可执行程序时,库文件的代码已经拷贝到代码区了。因此不需要运行时查找。
[问题二]:为什么动态库需要运行时查找?
答:因为程序和动态库是分开加载的。(以下的讲解需要对进程地址空间有一定的理解)
当我们将程序加载到内存中时,OS为我们的程序创建进程控制块PCB,并建立起进程地址空间。因为我们的代码中用到了动态库,因而有部分代码是需要跳转到动态库中执行的。
要想跳转到动态库中,前提是将动态库加载到内存。既然要加载到内存中,动态库就必须要能被找到。动态库加载到内存后,通过页表将内存中的动态库映射到堆栈之间的区域,这个区域就叫做共享区
。
我们的代码被存放在代码区,当需要执行动态库中的代码时,跳转到共享区即可,执行完毕再跳转回代码区中继续向下执行。由此我们可以在自己的地址空间中执行完所有的代码(自己的和动态库的)。
整个内存中,虽然动态库只有一份,但是通过进程地址空间,动态库可以被多个进程所共享,因此可以大大节省内存空间。