当前位置: 首页 > news >正文

动静态库的制作

文章目录:

  • 什么是程序库?
  • 动态链接和静态链接
  • 动静态库的认识
  • 静态库的创建与使用
    • 创建
    • 使用
  • 动态库的创建与使用
    • 创建
    • 使用

什么是程序库?

程序库:一般是软件作者为了发布方便、替换方便或二次开发目的,而发布的一组可以单独与应用程序进行 compile time 或 runtime 链接的二进制可重定位目标码文件。通俗来讲,一个库就是一个文件,这个文件可在编译时由编译器直接链接到可执行程序中,也可以在运行时由操作系统的 runtime environment 根据需要动态加载到内存中。

实际上,每个程序都需要依赖很多基础的底层库,因此库的存在是非常中重要的。库有两种:

  • 静态库(.a、.lib):程序在编译链接的时候把库的代码链接到可执行程序中。程序运行时将不再需要静态库。
  • 动态库(.so、.dll):程序在执行的时候才去链接动态库的代码,多个程序共享动态库的代码。

在这里插入图片描述

库命名约定: 库通常以前缀 “lib” 命名。对于所有的C标准库都是如此。在连接时,对库的命令引用将不包含库的前缀或后缀。

动态链接和静态链接

  • 动态链接:在可执文件开始运行之前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程就称为动态链接(dynamic linking)。

在这里插入图片描述

动态库存储在磁盘中,它可以由很多个进程共享,所以动态链接使得可执行文件更小。若某个程序需要使用某个动态库时,只需要在该进程的进程地址空间的共享区开辟空间,然后通过该进程的页表将物理内存中的动态库映射到该进行的虚拟内存中。

  • 静态链接:可执行程序编译链接时,将程序中使用到的静态库的代码拷贝到该可执行程序中。

动静态库的认识

如下所示,在 Linux 下编写一个简单的程序,接下来我们将用该程序来认识一下动静态库。

在这里插入图片描述

在该程序中我们通过调用 printf 来输出目标字符串,而 printf 是库函数。因此,在使用 gcc 编译此程序时,将C标准库也链接进来了。

在 Linux 下,可以通过指令 ldd 可执行程序名 来查看一个可执行程序所依赖的库文件:

在这里插入图片描述

从上图可看出,libc-2.17.so 实际上就是一个动态库。在 Linux 下,.so 为后缀的是动态库,.a 为后缀的是静态库。

gcc/g++ 编译器默认生成的二进制程序都是动态链接的,如果想要实现静态链接,可以在使用 gcc/g++ 编译文件时加上 -static 选项。

在这里插入图片描述

采用静态链接生成的可执行程序不依赖其它库文件,使用指令 ldd 可执行程序名 可以查看该可执行程序所依赖的库文件,如下所示 :

在这里插入图片描述

静态库的特点:

  • 静态库对库函数的链接是放在编译时期完成的。
  • 程序在运行时将与函数库无任何联系,便于移植。
  • 静态链接生成的可执行程序非常大,浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库将被链接形成一个可执行文件。

动态库的特点:

  • 动态库将一些库函数的链接载入推迟到程序运行的时期。
  • 可以实现进程之间的资源共享。(因此动态库也称为共享库)
  • 将一些程序升级变得简单。
  • 可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。

静态库的创建与使用

创建

静态链接库其实就相当于压缩包,其内部可以包含多个源文件。需要注意的是,并不是任意一个源文件都可以被加工成静态链接库,其至少需要满足以下两个条件:

  • 源文件中只提供可重复使用的代码,如:函数、设计好的类等,其中不能包含 main 函数;
  • 源文件在实现具备模块功能的同时,还需要提供访问它的接口,也就是各个功能模块声明部分的头文件;

接下来,我们将演示如何创建一个静态库,以下面四个文件为例,其中两个源文件 calc.cPrint.c ,两个头文件 calc.hPint.h

在这里插入图片描述

在这里插入图片描述

1、接下来先编译所有的源文件生成对应的目标文件:

在这里插入图片描述

2、一旦我们有了一个目标文件(或多个文件),就使用 GNU ar 命令来将所有的目标文件创建成最终的库:

ar 命令是 GNU 的归档工具,常用于将目标文件打包为静态库,下面我们将使用 ar -rc 命令来对目标文件进行打包。

  • -r replace :在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar 显示一个错误信息,并不替换其它同名模块。默认情况下,新的成员增加在库的结尾处,可以使其它任选项来改变增加的位置。
  • -c create : 创建一个库。不管库是否存在,都将创建。
ar -rc libcalc.a calc.o Print.o

在这里插入图片描述

我们可以使用 ar -tv 来查看静态库中包含的文件:

ar -tv libcalc.a 

在这里插入图片描述

3、将头文件和生成的静态库组织起来:

当我们将自己的静态库给别人使用时,实际上需要给出两个文件夹,一个文件夹下面存储静态库中的所有头文件,另一个文件夹下存储所有的库文件。

创建一个目录 mathlib ,在该目录下创建 include 和 lib 目录,将 calc.h 和 Print.h 这两个头文件放到 include 目录下,将生成的静态库文件 libcalc.a 放到 lib 目录下,然后就可以将 mathlib 给别人用了。

在这里插入图片描述

使用 makefile 将以上步骤组织起来,形成 makefile 文件。

libmath.a:calc.o Print.o
	ar -rc libmath.a calc.o Print.o

calc.o:calc.c
	gcc -c calc.c -o calc.o -std=c99
Print.o:Print.c
	gcc -c Print.c -o Print.o -std=c99 

.PHONY:output
output:
	mkdir -p lib-static/lib 
	mkdir -p lib-static/include
	cp *.a lib-static/lib 
	cp *.h lib-static/include

.PHONY:clean
clean:
	rm -rf *.o *.a lib-static 

写好 makefile 以后,我们就可以将静态库进行一键发布。首先使用 make 生成所有目标文件对应的源文件,然后 make output 将这些目标文件与静态库文件组织起来:

在这里插入图片描述

使用

Linux 下使用静态库,只需要在编译的时候,指定静态库的搜索路径(-L选项)、指定静态库名(不需要 lib 前缀和 .a 后缀,-l选项)、指定头文件的搜索路径(-I选项)。

  • -L:表明静态库的搜索路径
  • -l:指定链接时需要的库,编译器查找库时由隐含的命令规则,即在给出的i那个字前面加上 lib,后面加上 .a 或 .so 来确定库的名称。
  • -I:指定头文件的搜索路径
gcc test.c -I./lib-static/include -L./lib-static/lib -lmath

在这里插入图片描述

注意:-I-L-l 这三个选项后面可加空格,也可以将空格省略掉。

另一种方法:将头文件和库文件拷贝到存储系统头文件和系统库文件的路径下。

sudo cp lib-static/include/* /usr/include/
sudo cp lib-static/lib/* /lib64/

这个就不演示了。该方法采用的是直接将我们写好的库的头文件和库文件直接拷贝到系统路径下,虽然该方法比较简单,但是不推荐使用此方法,因为这样会对系统文件造成污染。

动态库的创建与使用

创建

共享库或动态链接库(dll)具有在多个程序之间共享一个库副本的巨大优势,因此称为共享库,将它们与多个程序链接的过程称为动态链接。接下来将演示如果在 Linux 上创建和使用共享库。

1、让所有源文件生成对应的目标文件:

在这里用源文件生成对应的目标文件时需要携带选项 fPIC,-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),而产生的代码中,没有绝对地址,全部使用相对地址,因此代码可以被加载器加载到内存的任意位置,都可以正确的执行。则共享库被加载时,在内存的位置不是固定的。

gcc -fPIC -c Print.c -o Print_so.c -std=c99
gcc -fPIC -c Print.c

在这里插入图片描述

2、使用 -shared 选项将所有目标文件打包为动态库

  • -shared :生成一个共享对象,该对象可以与其它对象链接以形成可执行文件
gcc -shared -o libcalc.so Print_so.o calc_so.o

在这里插入图片描述

3、将头文件和生成的动态库组织起来:

创建一个目录 lib_dyl ,在该目录下创建 include 和 lib 目录,将 calc.h 和 Print.h 这两个头文件放到 include 目录下,将生成的动态库文件 libcalc.so 放到 lib 目录下,然后就可以将 lib_dyl 给别人用了。

在这里插入图片描述

使用 makefile 将以上步骤组织起来,形成 makefile 文件。

libmath.so:calc_so.o Print_so.o
	gcc -shared -o libcalc.so calc_so.o Print_so.o
calc_so.o:calc.c
	gcc -fPIC -c calc.c -o calc_so.o -std=c99 
Print_so.o:Print.c
	gcc -fPIC -c Print.c -o Print_so.o -std=c99 

.PHONY:output
output:
	mkdir -p lib_dyl/lib 
	mkdir -p lib_dyl/include
	cp *.so lib_dyl/lib 
	cp *.h lib_dyl/include

.PHONY:clean 
clean:
	rm -rf *.o *.so lib_dyl

写好 makefile 以后,我们就可以将动态库进行一键发布。首先使用 make 生成所有目标文件对应的源文件,然后 make output 将这些目标文件与动态库文件组织起来:

在这里插入图片描述

使用

我们将动态库和测试代码拷贝到一个新的目录,接下来进行测试:

在这里插入图片描述

引用动态库编译成可执行文件(与静态库的方式一样),在使用动态库时也需要加路径,也需要使用 -l-I-L 这三个选项来生成可执行文件。

在这里插入图片描述

接下来运行:./test ,发现竟然报错了!!!生成的可执行程序不能正常运行。

在这里插入图片描述

那么猜测可能的原因,是因为动态库与测试程序不在同一个目录下,接下来进行验证:

在这里插入图片描述

经过测试后发现,动态库可以正常链接,可执行程序执行成功!但是,在实际中,动态库一般不会和我们自己的可执行在同一路径下。因此,该方法不实用。

这里需要注意一下,使用 -I-l-L 选项是让编译器能够找到我们使用的头文件和库文件所在位置,但是使用 gcc/g++ 生成可执行程序后,生成的可执行程序就和编译器没有关系了。当可执行程序运行时依旧找不到该可执行程序所依赖的库。

下面,将介绍四种方法来解决此问题。

1️⃣ :拷贝 .so 文件到系统共享库路径下,一般指 /usr/bin(不推荐此做法,拷贝库文件到系统库路径下会污染库)

sudo cp lib_dyl/include/* /usr/include/ 
sudo cp lib_dyl/lib/* /lib64/

在这里插入图片描述

上面只是一个演示,若不想将自己的库文件留着系统的库文件中,可以将它进行删除。

在这里插入图片描述

2️⃣:更改 LD_LIBRARY_PATH(配置完成之后,退出之后再次查看,)

LD_LIBRARY_PATH 是 Linux 系统下的环境变量名,类似于 PATH。它用于指定查找共享库(动态链接库)时除了默认路径(./lib 和 ./usr/lib)之外的其它路径。

使用场景:移植程序时经常需要使用一些特定的动态库,而这些编译好的动态库放在自己建立的目录中,这时可以将这些目录设置到 LD_LIBRARY_PATH 中。

在 Linux 下可以使用 export 命令来设置这个值:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hyr/linux_code/linux17/uselib_dyl/lib_dyl/lib

在这里插入图片描述

export 导入变量后在重启时会失效。可以在 ~/.bashrc 或者 ~/.bash_profile 中添加 export 语句,前者在每次登录和每次打开 shell 都会读取一次,后者只在登陆时读取一次。

例如:在 ~/.bashrc 文件末尾添加我们动态库所在的路径,如下所示:

在这里插入图片描述

在添加保存之后,需要关闭当前的终端并重新打开一个新的终端,从而使上面的配置失效。

3️⃣ :配置 /etc/ld.so.conf.d/,配置完成之后使用 ldconfig 更新,使配置生效

可以通过配置 /etc/ld.so.conf.d/ 来解决此问题,该路径下存储的全是以 .conf 为后缀的文件。这些文件中存储的都是一些路径,系统会自动在该路径下查找所有配置文件里面的路径,然后在每一个路径下查找是否有你需要的库,若查找到了,则你的程序可以正确的链接到动态库了。

在这里插入图片描述

以下演示一下操作:

在这里插入图片描述

4️⃣:使用软链接将路径指向库

我们使用自己创建的动态链接与系统库建立软链接,这样就可以使可执行程序正确链接了:

在这里插入图片描述
可以使用 unlink 来取消软链接关系,如下所示:

在这里插入图片描述

相关文章:

  • Python入门(4)语法、变量和标识符、数据类型、字符串、布尔值、类型检查、对象、类型转换、运算符
  • 接收机中的非线性因素来源与模型
  • 统计字典序元音字符串的数目 (回溯/dfs/动态规划/压缩/数学)
  • 5个python常用的装饰器!
  • OpenFeign 源码解读:动态代理+负载均衡实现
  • Java Web应用开发——作业四
  • 什么是谷歌快排技术,谷歌排名推广霸屏的原理
  • 01 | Qt基本介绍及环境搭建
  • FreeRTOS任务之调度器中的三种调度算法
  • 程序入参调优【自我提升】
  • 数据结构与算法(基于<algorithm>)
  • 【Linux】树状目录结构
  • [前端笔记038]vue2之vueRouter、elementUI
  • Java 8 - Lambda 表达式
  • 【开源软件】服务器状态监控通知平台
  • [数据结构]链表的实现在PHP中
  • django开发-定时任务的使用
  • happypack两次报错的问题
  • log4j2输出到kafka
  • Python十分钟制作属于你自己的个性logo
  • 成为一名优秀的Developer的书单
  • 初识 beanstalkd
  • 大整数乘法-表格法
  • 观察者模式实现非直接耦合
  • 基于web的全景—— Pannellum小试
  • 扑朔迷离的属性和特性【彻底弄清】
  • 容器服务kubernetes弹性伸缩高级用法
  • 软件开发学习的5大技巧,你知道吗?
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 微信开放平台全网发布【失败】的几点排查方法
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • 阿里云服务器如何修改远程端口?
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • ###项目技术发展史
  • #{}和${}的区别是什么 -- java面试
  • (31)对象的克隆
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (多级缓存)缓存同步
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (蓝桥杯每日一题)love
  • (学习日记)2024.01.09
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .java 9 找不到符号_java找不到符号
  • .NET中使用Protobuffer 实现序列化和反序列化
  • /bin/bash^M: bad interpreter: No such file ordirectory
  • @EventListener注解使用说明
  • @FeignClient注解,fallback和fallbackFactory
  • @拔赤:Web前端开发十日谈
  • []error LNK2001: unresolved external symbol _m