目录
一、静态库
1、静态库的概念
2、制作静态库的指令
3、制作静态库
4、链接静态库
二、动态库
1、动态库的概念
2、制作动态库的指令
3、制作动态库
4、链接动态库
5、动态库的加载
三、静态库与动态库的区别
结语
前言:
在Linux下大部分程序进行编译时,都会接触静态库或动态库,因此动静态库在Linux下是个很重要的概念,他们都是文件,其中静态库后缀是.a,动态库后缀是.so。动静态库里保存的是函数的具体方法,他们的不同点在于链接阶段时,静态库会把方法直接拷贝到程序中,而动态库会把方法加载到内存中,并且在可执行程序中记录了动态库在内存的地址,以便在运行时能够让可执行程序从内存中定位具体方法。
一、静态库
1、静态库的概念
静态库是由.o文件打包而来的,因此库里面保存的是二进制,程序链接静态库时,会将静态库里的方法拷贝至可执行程序,所以使用静态库会导致可执行程序变大,则会占用内存和磁盘的空间,但是该可执行程序运行时不再需要链接外部的库,程序的独立性较强。
2、制作静态库的指令
静态库的命名方式必须以lib为前缀,以.a为后缀,使用指令生成静态库:
ar -rc libxxx.a xxx.o
//将xxx.o文件打包成静态库
这里.o文件可以是多个,即可以将多个.o文件打包成静态库,由于静态库存放的是函数方法,所以要先有一个.c源文件存放函数实现,以及有一个.h文件存放函数声明,代码如下:
//math.h
#pragma once
#include<stdio.h>
int Add1(int x,int y);
//math.c
#include"math.h"
int Add1(int x,int y)
{
return x+y;
}
并且在主函数中调用函数add:
//test.c
#include"math.h"
int main()
{
int sum = Add1(5,3);
printf("sum=%d\n",sum);
return 0;
}
先测试以上代码是否正确:
从结果看以上代码的逻辑没问题。
3、制作静态库
写一个makefile,便于完成静态库的制作,makefile的具体工作:生成math.c的.o文件,然后打包.o文件生成静态库文件,并且创建一个目录,将.h和.a文件打包到该目录下,这样直接将整个目录给到外部使用,.h文件主要是让用户知道该静态库有哪些方法可以被调用,类似说明书的作用。
makefile代码如下:
libmath.a:math.o
ar -rc $@ $^
math.o:math.c
gcc -c $^ -o $@
.PHONY:clean
clean:
rm -f *.o *.a
.PHONY:mk
mk:
mkdir -p lib_math/include
mkdir -p lib_math/libmath
cp *.a lib_math/libmath
cp *.h lib_math/include
测试结果如下:
4、链接静态库
二、动态库
正常编译一个程序时,用的指令是gcc xxx.c -o a.out,该指令默认让编译器和链接器到指定的路径下去寻找.so(动态库)文件,给指令加上-static后就去指定的路径下寻找.a(静态库)文件,共同点是寻找的路径是被设置好的(寻找#include的头文件也是如此,系统默认到指定路径下找头文件),很明显上面自己制作的静态库是在我们自己创建的目录下,所以需要对默认指令进行路径指定化,并且寻找头文件math.h也需要指定路径,操作如下:
gcc test.c -I ./lib_math/include -L ./lib_math/libmath -lmath
测试结果:
这里要注意两个细节:
1、-L的时候要精确到.a文件,-I的时候不需要精确到具体的.h文件是因为代码中已经用include包了那个.h文件。
2、链接.a文件的时候用-l并且后面紧跟库的名称,并且该库的名称要去掉前缀和后缀,即只留下和#include<>尖括号中一样的名称。
运行a.out:
发现运行结果正确,并且细心观察此时a.out文件内容较大,是因为静态库在程序链接时会将对应方法拷贝到程序中形成最终的可执行程序。
1、动态库的概念
动态库也是由.o文件打包而来的,生成.o文件后对.o文件进行加工得到动态库,跟静态库不同的是,他的文件后缀是.so,并且动态库类似可执行程序(静态库是一个文件),因为他需要被加载到内存中以便让多个进程共用其方法,所以他不必拷贝到程序中节省了内存和磁盘的空间。
2、制作动态库的指令
首先使用gcc编译指令形成.o文件,只不过该指令中需要加上选项-fPIC,指令如下:
gcc -fPIC -c xxx.c
//执行该指令得到一个xxx.o文件
//fPIC:产生位置无关码,即偏移量
//因为动态库加载到内存中是不固定地址的,
//所以需要根据偏移量找到在进程地址空间中加载的动态库的方法位置,即动态库的起始地址+偏移量。
接着对生成的.o文件进行打包即可生成动态库,和制作静态库不一样,制作动态库的指令也是gcc,只不过需要加上-shared选项,并且对动态库的命名方式需加前缀lib和后缀.so:
gcc -shared -o libxxx.so xxx.o
//执行该指令得到一个xxx.so的动态库
3、制作动态库
1、沿用上文的头文件、源文件,首先用math.c生成动态库所需的.o文件:
2、打包.o文件形成动态库:
3、和上述静态库一样的逻辑,将.h文件和.so文件放到一个目录下:
4、链接动态库
因为编译的指令始终是gcc,所以链接动态库的指令和链接静态库是一样的,又因为是链接我们自己制作的库,所以需要用-I和-L来指定路径,并且连接指定库时要去掉前缀lib和后缀.so,链接指令如下:
gcc test.c -I ./lib_math/include -L ./lib_math/libmath -lmath
测试结果:
从结果看果然生成了a.out,这一切看起来都很顺序,然后运行a.out后发现:
报错的原因是找不到动态库,虽然在gcc指令中指定了库的路径,但是这只是让编译器在编译的时候可以找到库,然而动态库本身需要被加载到内存中,所以动态库也需要让加载器知道动态库的路径,而加载器默认会去/lib64这个目录下查找,所以我们需要通过某种方式让我们的的动态库出现在这个目录下。
用软链接的方式:
发现结果是可行的,因此得出一个结论:连接动态库还需要将库文件安装到系统下,并且动态链接后形成的可执行程序确实比静态链接形成的可执行程序要小,原因就是程序中用到的动态库方法被加载到内存中的共享区供所有进程使用,并没有直接拷贝到程序中。
5、动态库的加载
从上文可以知道,动态库会被加载到内存中,可执行程序会保存动态库在内存中的地址,以便在程序运行时能够根据该地址找到具体的方法,动态库只会被加载一份至物理内存中,只不过多个进程的共享区会指向同一个物理内存,这样一来就无需加载相同的库文件至内存中。动态库和内存以及进程的关系示意图如下:
三、静态库与动态库的区别
1、静态库在链接时会把调用方法拷贝到程序中,从而形成最终的可执行程序。动态库不会直接拷贝,而是在程序运行时加载到内存中,让程序动态调用。
2、静态库导致可执行程序变大。而动态库不会让可执行程序变大。
3、制作静态库的指令是ar -ra。制作动态库的指令是gcc -shared -o,并且需用-fPIC形成.o文件。
4、使用静态库时无需将静态库放到系统路径下。使用动态库时需要将动态库放到系统路径下。
5、静态库独立性强,依赖性低。动态库独立性低,依赖性强。
结语
以上就是关于Linux的动静态库讲解,只要我们的程序用到了第三方库那么就离不开动静态库,因此大部分的程序都要和动静态库打上交道,动静态库最核心的点在于静态库会加载到程序中,而动态库会加载到内存中,通过这两点再向外拓展。
最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!