静态库( .a ):在程序编译的时候,把库的代码链接(拷贝)到可执行程序。
动态库(.so):在程序的执行时,链接动态库的代码。多个程序同时共享代码。
创建一个静态库:
创建计算器,带有加减乘除功能。
声明和定义分离。创建文件
所有文件:
头文件 .h
源文件.c
利用指令gcc -c
gcc -c Add.c Sub.c Div.c Mul.c
为了简化操作,我们只测试加法功能
创建testadd目录 将Add.o文件移动到testadd目录,创建一个简单的test代码
包含上层目录的Add.h头文件
1 #include"../Add.h"
2
3 int main()
4 {
5 int ret=add(3,5);
6 printf("%d\n",ret);
7 return 0;
8 }
将test.c文件编译成test.o文件
因此用户可以通过包含头文件的形式 再编译成可执行文件
因此通过将.o文件和.h文件打包给别人,别人就能使用库。
这一个将.o文件拷贝编译生成可执行可执行程序的过程就是静态链接。
但是有果有许多的.o文件,打包起来就很繁琐,用户使用也不舒服。
介绍第二种方式
1)生成静态库
ar是gun归档工具,常用于目标文件打包成静态库
r-c表示replace-create
指令:
ar -rc libmymath.a add.o sub.o
生成静态库libmymath.a
利用自动化工具(Makefile)
这一的make操作,我们希望将所有的.c文件(不包含main函数)打包成libmymath.a文件
%.o :%.c
gcc -c $<
.o 文件依赖 .c
依赖方法是gcc -c
$< 是将所有的.c文件一个一个指定方法
ar -rc将所有.o生成libmymath.a
自动化构建
通过 ar - tv指令查看静态文件中的目录列表
ar -tv libmy_math.a
2)将头文件和库打包(发布)
将我们的库给别人实际上是给出俩个文件。一个文件包含库,一个文件包含头文件
使用者只需要包含头文件就能使用我们的库文件
下面修改我们的Makefile自动化建构文件
创建mymath_lib目录
底下包含俩个子目录
include (用来放头文件)
lib(用来放库文件)
1 static-lib=libmy_math.a
2
3 $(static-lib):Add.o Div.o Mul.o Sub.o
4 ar -rc $@ $^
5 %.o:%.c
6 gcc -c $<
7 .PHONY:output
8 output:
9 mkdir -p mymath_lib/include
10 mkdir -p mymath_lib/lib
11 cp -f *.h mymath_lib/include
12 cp -f *.a mymath_lib/lib
13
14 .PHONY:clean
15 clean:
16 rm -rf *.o *.a
17
make构造
tree查看目录mymath_lib
这样就完成对静态库的创建,如果我们想发送出去。我们还可以进行cxz压缩文件
我们有一份静态库mymath_lib
在使用时,必须指明链接哪一个库,哪个路径
如果直接gcc编译会出错
原因是找不到库 " "会再当前目录下寻找文件,找不到再全系统目录下寻找
方法一:(不推荐)
将mymath_lib的头文件和库文件分别拷贝到系统目录中
系统头文件一般在/user/include/路径下
系统库文件一般放在/lib64/路径下
然后编译的时候通过 gcc 文件名 +l指明要链接的第三方库的名称
不推荐的原因操作相对繁琐,会污染系统库,不想使用了需要自行删除
方法二:(推荐)
在gcc编译时候 ,通过-I(小写i) 和 L(大写L)指定路径和库
gcc test.c -I./mymath_lib/include
gcc test.c -I./mymath_lib/include -L./mymath_lib/lib/
gcc test.c -I./mymath_lib/include -L./mymath_lib/lib/ -lmymath
最后在指定路径和库后终于完成编译,就产生了可执行程序
动态库的创建,大致与静态库相同,有些许差异
下面新建一个目录,存放计算器的四个.c和.h文件
生成动态库需要添加与位置无关码 -fPIC
说明:
- fPIC选项是在编译过程告诉编译器生成与绝对路径无关的相对存储方式。保证代码被加载到内存的任意位置,都能通过位置无关码(相对路径)找到/访问。
- 我们总是以fPIC产生.so文件,如果不带fPIC也可以编译动态库。如果不带,在内存加载数据时,总是会被重新定位,没有固定的位置。
- 因此-fPIC能够实现共享。后续将会做详细的解释。
生成.o文件之后,不再使用ar打包而是在gcc 选项之后添加shared
下面依旧利用自动化构建工具(先生成.o在整合生成.so)
与静态库生成不同的是,动态库完全由gcc就能完成
dy-lib=libmymath.so
2
3 $(dy-lib):Add.o Div.o Mul.o Sub.o
4 gcc -shared -o $@ $^
5 %.o:%.c
6 gcc -fPIC -c $<
7
8 .PHONY:clean
9 clean:
10 rm -f *.o *.so
与静态库的生成一样,需要将库发布。发布就需要放在同一个目录下的俩个不同目录下
创建库目录my_math_lib
子目录 include放头文件
lib放库 .so
完善我们的自动化构建
增加output功能
1 dy-lib=libmymath.so
2
3 $(dy-lib):Add.o Div.o Mul.o Sub.o
4 gcc -shared -o $@ $^
5 %.o:%.c
6 gcc -fPIC -c $<
7
8 .PHONY:output
9 output:
10 mkdir -p my_math_lib/include
11 mkdir -p my_math_lib/lib
12 cp -f *.h my_math_lib/include
13 cp -f *.so my_math_lib/lib
14 .PHONY:clean
15 clean:
16 rm -f *.o *.so
tree一下结构
完成动态库的制作和发布
在该路径底下只有动态库文件和用户.c文件
依照静态库的方式,
指定头文件路径I(大写i)
库文件路径(大写L)
指定库名称l
但还是找不到文件
因为我们之前指定的路径和库是为了让gcc完成编译
在运行时,就与编译无关. OS找不到对应的库
ldd指令查看库的链接
系统链接不到.so动态库
解决方法:让系统找到动态库
对于网上下载的库,最推荐这个做法,简单省事
需要切换到root目录下
sudo cp my_math_lib/lib/libmymath.so /lib64
sudo ln -sf /home/user_Lian/day16/testdy/my_math_lib/lib/libmymath.so /lib64/libmymath.so
此时在/lib64路径下就存在同名文件 文件的内容指向库
在xshall重启后依旧有效
LD_LIBRARY_PATH
使用环境变量让系统找到自己的库
export导入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user_Lian/day16/testdy/my_math_lib/lib/
ldd查询链接情况
链接成功,可以运行程序
但是重新启动Xshall后,环境变量会丢失
解决了环境变量重启丢失的问题
在系统配置文件 /etc/ld.so.conf.d/下创建文件
sudo vim /etc/ld.so.conf.d/myfile.conf
用vim打开,打入我们的路径
输入dudo ldconfing
系统会加载配置的文件
先来谈一下fPIC位置无关码
是一种起始地址加偏移量的关系;
通过保存起始位置和往起始位置后放偏移的位置就能找到相关数据
不管起始的地址在内存的哪一个位置,都是通过偏移量找到,这就是位置无关码
可执行程序:LEF格式
动态链接的程序,不光光要要加载程序,库也要加载。
程序在编译生成二进制后,没有加载到内存,程序内部也必须有地址!
代码被编译成二进制之后,就再也没有函数名、变量名,被编成地址数字。 是call(xxxx)的形式。
在编译时,对代码的编址,同样遵循虚拟地址空间,是基地址+偏移量的方法。所有代码会全部被放到代码段,是有范围的空间。
再谈一下映射关系
假如在ELF可执行程序中包含俩个库 libc.so mymath.so
程序加载到物理内存是以kv的形式,物理内存再与地址空间通过页表建立映射关系。
同时库也必须加载到内存,通过页表映射到地址空间的共享区。
如果可执行程序调用库函数实际上是一种call(偏移量的形式),由于库映射到共享区,基地址依旧是确定的。再通过调用某一个函数得到的偏移量。基地址+偏移量就能在共享区找到那一个特定的方法。
如果有一个进程,同时使用使用了libc.so库,如果想用库的方法,就不需要再加载库到内存中,通过映射关系到共享区中,修改起始地址,就能使用方法。
这就是动态库,只需要一份库加载到内存就能供任意多可执行程序使用。能够有效节约空间。