🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
目录
- 👉动静库和静态库👈
- 👉制作动静态库👈
- 制作静态库
- 制作动态库
- 👉库的意义👈
- 👉总结👈
👉动静库和静态库👈
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。而静态链接是将库的代码链接到可执行文件中,因此该可执行文件往往比较大。
👉制作动静态库👈
在工程项目开发中,我们往往会用到第三方提供的库。那第三方的库是如何制作出来的呢?第三方制作出来的库,是如何提供给我们使用的呢?为了回答两个问题,我们现在就来学习一下动静态库的制作。
制作静态库
为了更好地说明演示库的制作,我们先回顾一下以前的多文件的写法。头文件中放库中的头文件的包含和函数的声明等,源文件中放函数的实现等。
// myprint.h
#pragma once
#include <stdio.h>
#include <time.h>
extern void Print(const char* str);
// mymath.h
#pragma once
#include <stdio.h>
extern int addToTarget(int from, int to);
// myprint.c
#include "myprint.h"
void Print(const char* str)
{
printf("%s [%d]\n", str, (int)time(NULL));
}
// mymath.c
#include "mymath.h"
int addToTarget(int from, int to)
{
int ret = 0;
for(int i = from; i <= to; ++i)
{
ret += i;
}
return ret;
}
// main.c
#include "myprint.h"
#include "mymath.h"
int main()
{
Print("hello world");
int ret = addToTarget(0, 100);
printf("ret = %d\n", ret);
return 0;
}
输入gcc main.c mymath.c myprint.c -o my.exe -std=c99
指令就可以生成可执行程序 my.exe了。
假如现在我们是库的制作者,如果我们将头文件 .h 和目标文件 .o 给别人,别人是否可以实现我们所写好的库呢?其实是可以的,见下图所示:
将目标文件提供给别人去编译,是不方便别人使用的。因为目标文件多了,编译起来就会比较麻烦,而且传输的过程中还可能出现目标文件丢失的情况。所以我们需要将目标文件打包,而形成的包就是传说中的静态库了。
将目标文件打包成静态库指令
ar -rc libhello.a mymath.o myprint.o
#以下是注释
#ar是GNU中的归档工具 archive 归档
#-r replace 替换
#-c create 创建
#lib 库的前缀 .a 静态库的后缀 两者之间的内容就是静态库的名字
使用Makefile来生成静态库
libhello.a:mymath.o myprint.o
ar -rc libhello.a mymath.o myprint.o
mymath.o:mymath.c
gcc -c mymath.c -o mymath.o -std=c99
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o -std=c99
.PHONY:clean
clean:
rm -rf *.o libhello.a
现在我们已经制作好了静态库,那么如果将这个静态库发布给别人使用呢?发布给别人使用的库中是包含 include 和 lib 两个文件夹,其中 include 中包含库的所有头文件,lib 包含的是对应的库文件。
那么,我们修改一下 Makefile,让它可以自动生成上方所述的路径。
libhello.a:mymath.o myprint.o
ar -rc libhello.a mymath.o myprint.o
mymath.o:mymath.c
gcc -c mymath.c -o mymath.o -std=c99
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o -std=c99
.PHONY:hello
hello:
mkdir -p hello/include
mkdir -p hello/lib
cp -rf *.h hello/include
cp -rf *.a hello/lib
.PHONY:clean
clean:
rm -rf *.o libhello.a hello
将其他文件清理后,就剩下静态库和我们写的程序了。
接下来,我们就来学习一下如何使用静态库。
- 将静态库拷贝到系统的默认搜索路径。头文件的默认搜索路径是
/usr/include/
,库文件的默认搜索路径是/lib64/
或者/usr/lib64/
。
现在我们已经将静态库拷贝到了对应的路径下,那我们使用gcc main.c
指令将代码编译一下,就会发现无法通过编译。原因是 gcc 默认链接的静态库是/lib64/libc.a
,如果想让 gcc 链接我们写的静态库,就需要使用下方的指令了。
gcc main.c -lhello
#-l 链接 hello是静态库的名字
注:将库拷贝到系统的默认路径下,就叫做库的安装。
将我们自己写的静态库拷贝到系统的默认链接下这种做法是不推荐的。因为我们自己写的库并没有经过可靠性验证,所以不太建议这种做法。那么我们需要将自己写的静态库移出系统的默认路径,该过程也称为库的卸载。
- 指定头文件、库的路径和要链接的库
gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello
#-I 指明头文件所在的路径
#-L 指明库的路径
-l 指明所要路径的库(因为库路径下可能不止一个库)
制作动态库
静态库的代码是会被拷贝进可执行程序中;而动态库在没有被链接前会被编译好,当使用动态库时,并不是将代码拷贝到可执行程序中,而是让可执行程序与动态库产生关联。
生成目标文件
gcc -fPIC -c mymath.c -o mymath.o -std=c99
gcc -fPIC -c myprint.c -o myprint.o -std=c99
readelf -S 目标文件名 #查查看ELF格式的文件信息
-fPIC 选项的意思是形成与位置无关的目标二进制文件。动态库形成后,可以在内存的任意位置加载。而静态库的代码是拷贝到可执行程序中,可执行程序是有自己的地址空间的,所以静态库的代码需要拷贝到地址空间的特定位置,这就是与位置有关。静态库是按照绝对编址的方式,而动态库是按照相对编址(段地址+偏移量)的方式。
将目标文件打包
动态库的打包并不是使用 ar 指令,而是采用 gcc 加上 -shared 选项将目标文件打包成库。注意:一定要加上 -shared 选项,不然会被 gcc 识别成要生成可执行程序,从而报出没有 main 函数的错误。
gcc -shared myprint.o mymath.o -o libhello.so
以上就形成了动态库了,那么我们也来学习一下用 Makefile 来生成动态库。
.PHONY:all
all:libhello.so libhello.a
libhello.so:mymath_d.o myprint_d.o
gcc -shared mymath_d.o myprint_d.o -o libhello.so
mymath_d.o:mymath.c
gcc -fPIC -c mymath.c -o mymath_d.o -std=c99
myprint_d.o:myprint.c
gcc -fPIC -c myprint.c -o myprint_d.o -std=c99
libhello.a:mymath.o myprint.o
ar -rc libhello.a mymath.o myprint.o
mymath.o:mymath.c
gcc -c mymath.c -o mymath.o -std=c99
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o -std=c99
.PHONY:output
output:
mkdir -p output/include
mkdir -p output/lib
cp -rf *.h output/include
cp -rf *.a output/lib
cp -rf *.so output/lib
.PHONY:clean
clean:
rm -rf *.o *.a *.so output
将动静态库拷贝到 uselib 路径下
链接动态库
gcc main.c -I output/include/ -L output/lib/ -lhello
当存在同名的动静态库时,使用上方的指令形成的可执行程序是无法执行的,其报错原因是无法找到对应的动态库。那为什么会这样呢?
当output/lib/
路径下只有静态库时,却又可以生成对应的可执行程序,这又是为什么呢?
原因是 gcc 默认链接的是动态库,而如果只有我们自己制作的静态库,没有自己制作的动态库,那么就只能链接该静态库了。注意:系统的动态库还是动态链接的,只是静态链接自己制作的静态库。如果想让全部库都是静态链接的话,需要加上 -static 选项。-static 的意义就是摒弃优先使用动态库的原则,而是直接使用静态库。
如果存在同名的动静态库,默认使用的就是动态库,上面的报错只是找不到而已。那如何解决呢?解决这个问题,就需要了解动态库的加载了。
动态库的加载
如果是静态链接,静态库中的代码会被拷贝到可执行程序中。当可执行程序运行时,静态库的代码也被加载到内存了。而如果采用的是动态链接,可执行程序和动态库是分批加载的,并不是一起加载的。我们知道栈区和堆区之间存在一个共享区,而动态库的代码就是被加载到进程地址空间的共享区中去了。
当有多个进程使用同一个动态库的代码,这时候就不需要加载动态库的代码,只需要建立页表映射关系就行了。如果多个进程使用同一个静态库,那么内存中就会有多份静态库的代码,这样就会浪费内存空间了。这也就是动态库的意义。
当我们执行可执行程序时,不是已经指定了动态库所在的路径了吗?为什么还是找不到动态库呢?其实这是告诉 gcc 编译器,并不是告诉给操作系统的加载器。当程序运行加载时,就和 gcc 没有关系了,所以我们需要将动态库所在的路径告诉给操作系统的加载器,这样才能够找到动态库。
因为 C语言的动态可以就在系统的默认路径下,所以操作系统就能够找到对应的动态库;而静态库的代码是直接拷贝到可执行程序中的,不存在需要找的问题。那么,我们如果让操作系统找到自己制作的动态库呢?第一种方式就是将自己制作的动态库拷贝到系统的默认路径下,但这种方式不好。我们可以采用下方的方法:
- 将动态库所在的路径导入到环境变量 LD_LIBRARY_PATH 中
系统可以通过环境变量 LD_LIBRARY_PATH 找到需要的动态库,将其加载到内存中。
这种方法有一个缺点,就是当我们将 Xshell 关掉时,再次启动时,会将导入到 LD_LIBRARY_PATH 中的路径都清空掉。
- 新增配置文件
我们只需要在/etc/ld.so.conf.d/
路径下创建一个.conf
后缀的文件,该文件中保存动态库的路径,最后输入sudo ldconfig
指令更新配置文件即可。
将配置文件删除后,还是找到我们的动态库的。原因是缓存中还有该配置文件,只要将配置文件更新一下就可以了。
- 在
/lib64/
路径下建立一个与动态库的软链接
还有一种方法就是修改登录脚本,Linux 的登录脚本文件有两个,分别是 .bashrc
和 .bash_profile
,它们都是在家目录下的。.bash
是被.bash_profile
所调用的,那么我们可以在.bash_profile
文件中增加动态库所在的路径即可,但这种方式不建议现在做。
以上就是动态库的制作了,现在我们来了解一下为什么要有库。
👉库的意义👈
- 站在使用库的角度,库的存在,可以大大减少我们开发的周期,提高软件本身的质量(健壮性等)。
- 站在写库的人的角度,1. 简单 2.代码安全。
- 好玩的库:ncurses(基于字符的界面库),搜索关键词 Centos 7 yum 安装 ncurses。boost(C++ 的准标准库)。
👉总结👈
本篇博客主要讲解了什么是动静态库以及动静态库的制作等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️