linux系统编辑入门-库(静态库和动态库)的详解、制作与使用。

发布于:2023-01-17 ⋅ 阅读:(595) ⋅ 点赞:(0)

1. 动静态库的概念

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。

  • 静态库在链接阶段将汇编成的目标文件与引用到的库一起链接打包到 .exe 文件中,对应的链接方式称为静态链接。
  • 静态库对库函数的链接实在编译时期完成的。
  • 静态链接的程序在运行时与函数没有关系,方便移植。

动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
  • 与动态库链接的可执行文件,仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

2. 库与动静态链接

  • 为了开发效率和鲁棒性(健壮性),我们需要通过 库、开源代码、 基本的网络功能调用(各自网络接口,语音识别)来使用别人的功能。

库:静态库(一般命名为 lib+库名.so)、动态库(一般命名为 lib+库名.a)。生成可执行程序的方式有两种:静态链接、动态链接。

在这里插入图片描述

  • 通过touch mytest.cpp 创建一个.cpp文件。然后输入代码
    在这里插入图片描述
  • 编译cpp文件得到 .o 可执行文件,随后执行可执行文件即可。
    在这里插入图片描述
  • ldd gcc_test(ldd + 可执行程序名)(查看可执行程序的动态链接关系)。
    在这里插入图片描述
    在linux当中,默认情况下形成的可执行程序gcc_test是动态链接的,可以看到倒数第三行是 .so为后缀的。
  • 如果想使用静态链接,那就在gcc后+ “-static“。并采用 file gcc_test (file + 可执行程序名)(查看可执行程序的静态链接关系)。
    在这里插入图片描述
  • 动态链接的特点:体积小,节省资源(磁盘, 内存),但是一旦库丢失,bin不可执行。
  • 静态链接的特点:体积大,浪费资源(磁盘,内存),不依赖库,库丢失不影响可执行程序。

3. 静态库的制作与使用

3.1 制作静态库

在这里插入图片描述

\quad 若要给别人用自己写库又不想将源代码暴露给别人,就需要打包成静态库。
\quad 将以下.c 文件生成静态库。

  • 首先将自己想打包的代码写好
    在这里插入图片描述
    我们想让别人能使用我们的库,前提是别人首先需要知道你的库能提供怎样的功能。此时,就是通过头文件体现。
  • 随后将 .c 文件生成静态库:
gcc -c add.c sub.c  //得到 add.o 与 sub.o 文件。(.o 为可执行文件)
ar rcs libcalc.a add.o sub.o  //得到静态库文件 libcalc.a。
// ar 为了创建 .a (库文件);
// rcs :r 表示插入,c 表示创建,s 表示建立索引;
// libcalc : libcalc 表示库文件名,calc表示库名称;如 lib***.a。
// add.o sub.o :表示可执行文件。

在这里插入图片描述
注:我们只需要将头文件和这个刚刚生成的库文件libcalc.a 给别人就好,别人就可使用该库,形成自己的可执行程序。

3.2 使用静态库

  • 使用tree可得程序树状图,通过命令把 .h 文件放在include文件中; .a文件放在lib文件夹中; .c放在src文件夹中;.o 放在build文件夹中;
    在这里插入图片描述
  • 接下来,自己写一个main.c程序来使用这个库:
// main.c
#include<stdio.h>
#include"add.h"
#include"sub.h"
int main()
{
    printf("a = 10 , b = 20 \n");
    printf("a + b = %d\n",myadd(10,20));
    printf("a - b =%d\n",mysub(10,20));
    
    return 0;
}
  • 编译 main.c:
gcc main.c -o app -I ./include/ -l calc -L ./lib/
//  gcc main.c -o app //编译main.c 文件获得 app.out 文件;
//	-I(i大写)	    //表示查找头文件所包含的路径;
//	./ 			    //当前路径 ./include表示头文件在当前include目录;
//	-l(L 小写)    //表示加载哪个库;
//	calc 		   //库名称;
//	-L 			   //找库的路径;
//	./lib 		   //到lib目录下找calc 。

./app 执行可执行文件app.out 。

在这里插入图片描述

4. 动态库的制作与使用

4.1 制作动态库

在这里插入图片描述

  • 还是先对上面的add.c 与 sub.c 进行编译,再链接成动态库:
gcc -c -fpic add.c sub.c //首先得到 .o 可执行文件
gcc -shared add.o sub.o -o libcalc.so //获得动态库文件libcalc.so (-o 表示动态文件) 与 libcalc.a 类似。

-fpic用于gcc编译阶段,产生的代码没有绝对地址,全部用相对地址。这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic,那么生成的代码就会与位置有关,当进程使用该.so文件时都需要重新定位,且会产生成该文件的副本,每个读本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。为了保证这个库当中的代码地址不随着加载到内存的位置发生变化而变化,需要告诉gcc,我们进行-fpic选项产生与位置无关码进行编译。

在这里插入图片描述

  • 通过mv命令把文件放在各自文件夹中(.h 文件放在include文件中; .a文件放在lib文件夹中; .c放在src文件夹中;.o 放在build文件夹中):
    在这里插入图片描述
  • 用tree查看:
    在这里插入图片描述

4.2 使用静态库

  • 先编译再执行, 但是动态库不行。编译后,如果直接用 ./app 来执行,会报错,如下图:
gcc main.c -o app -I ./include/ -l calc -L ./lib/  //使用动态库,并编译main.c生成可执行文件
./app // 执行可执行文件

在这里插入图片描述
注意:我们会发现可执行程序无法正常运行。但此时是和编译器gcc没有关系的。

为什么静态库生成的可执行程序能直接运行而动态库不行?

  • 错误分析:
    程序启动之后,动态库会被动态加载到内存中,通过ldd(list dynamic dependencies)命令检查动态库依赖关系。静态库:gcc进行链接时,会把静态库中代码打包到可执行程序中;动态库:gcc进行链接时,动态库的代码不会被打包到可执行程序中。
  • 总结:
    在编译时候,只是把动态库信息保存到可执行程序中,并不会加载动态库的内容到内存中,所以编译时候不会报错;
    在运行时候,用到了动态库中内容,就需要把动态库加载到内存中,由于找不到动态库文件,所以就会出现上图中的报错。
  • 因此我们在编译之前先定位到共享库文件(即动态库文件lib***.so):通过环境变量LD_LIBRARY_PATH,把库的路径导入到环境变量中。
//将库导入到环境变量 (.so 文件在lib中所以添加在路径中)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/yellowone/Test/lib_s_d/lib_dynamic/lib/ 

在这里插入图片描述

  • 定位到共享库位置后,再进行编译,就运行成功了。可通过pwd来查看当前Terminal 所在路径,再导入进去即可,如上图所示。

5. 动静态库小结

静态库的优缺点

优点:

  • 静态库被打包到应用程序中,加载速度快;
  • 发布程序无需提供静态库,移植方便。

缺点:

  • 消耗系统资源,浪费内存;
  • 更新、部署、发布麻烦。

注:静态库不能多个程序共享,多个程序需要使用同一静态库时,都需要加载,故浪费内存。静态库文件改动后,每个用到同一静态库的程序都需要重新编译、部署、发布。因此,更新、部署、发布麻烦。

动态库的优缺点

优点:

  • 可以实现进程之间资源共享;
  • 更新、部署、发布简单;
  • 可以控制何时加载动态库。

缺点:

  • 加载速度比静态库慢;
  • 发布程序时需要提供依赖的动态库。

注:当程序使用到动态库时,动态库才加载进去,此后其他程序可以共享同一个动态库。当动态库更改时,只需要更新下动态库,而应用程序不需要再进行编译、部署、发布。

\quad 静态库、动态库区别来自链接阶段时如何处理,链接成可执行程序。分别称为静态链接方式动态链接方式
\quad 什么时候使用静态库和动态库:一般情况下,库比较小的时候使用静态库,库比较大的时候使用动态库。