目录
一.静态库
1.理解静态库
a.什么是静态库?
b.创建静态库的理论?
2.打包静态库
3.静态库的使用方法
a.头文件找不着
b.链接报错——库函数文件找不着
4.将静态库文件写到系统目录下
a.直接拷贝
b.建立软链接
二.动态库
1.什么是动态库?
2.打包动态库
3.动态库的使用方法
4.静态链接和动态链接
5.解决方案
环境变量
三.动态库加载(底层原理)
1.前置知识
2.动态库加载(首地址+偏移地址)
一.静态库
1.理解静态库
a.什么是静态库?
简单来说,就是把一些功能接口(函数)打包到一个库里,供他人使用,当第三方使用库内的某一函数时,函数的实现会以拷贝的形式插入代码中,这种函数库,我们将其称为静态库。
b.创建静态库的理论?
我们知道,将一个.c源文件转换成一个可执行程序,需要经过 编译 和 链接 这两个步骤。即,将.c文件编译成.o文件,再将可能用到的所有.o文件链接起来,生成可执行程序。
所以,如果我们想要打包一个静态库,首先要将所有的.c文件编译成.o文件,然后在把所有的.o文件用特定的指令打包成一个库文件!
2.打包静态库
把包含库函数的多个.c文件用 gcc -c file.c 命令编译相应的.o文件,然后用ar -rc libmymath.a file.o 命令将所有.o文件打包成库的形式
如果我们想要将上述的库提供给他人使用,除了需要将 库函数文件(libmymath.a)提供给对方外,还需要将库函数所用到的头文件(Add.h、Del.h、Mul.h、Sub.h)提供给对方。
所以,我们可以将静态库分成两部分:①库函数文件(libmymath.a);②头文件。如果我们将这两部分放在同一个目录下,并将该目录压缩打包,然后就能把这个压缩包发给被人使用啦~~
3.静态库的使用方法
接下来,我们站在对方(使用我们提供的静态库的人)的视角,来看看第三方静态库究竟是如何使用的~~
a.头文件找不着
首先肯定是对压缩包进行解压操作,得到一个目录,该目录下有库函数文件(libmymath.a)和头文件,如下图所示:
接下来,我们创建一个test.c文件,并在该文件中使用静态库所提供的功能接口,试一试~~
那么,编译test.c文件,能否成功呢?
出错啦~~
头文件目录明明就在mymath_lib目录中,系统为什么还说找不到呢??
原因:系统在展开头文件时,只会到两个地方找:①系统的 /user/include/ 目录;②文件所在当前目录。若两个地方都找不到,则报错!!
虽然 mymath_lib 与 test.c 处在同一个目录下,但 Add.h 可不在这里!!
解决方法:
b.链接报错——库函数文件找不着
再次编译 test.c 再试一试~~
报错原因:系统在链接时,会去 /lib64/ 目录下寻找库函数文件,找不着,就报错!!
解决方法:
编译时可以用 -I 指定“头文件”路径、用-L指定“静态库”路径、用 -l 指定所链接的“目标库名”
运行试一试~~
成功啦~~
虽然使用成功了,但是... 每次使用这个库时都要以相对路径的方式包含头文件,而且,编译时还得加上-L 这样的字段,是不是太麻烦了? 嗯~~ 有木有一劳永逸的解决方法嘞? --- 哎,还真有!!
4.将静态库文件写到系统目录下
系统头文件所在的目录: /usr/include/
系统库函数文件所在目录: /usr/local/lib/
由于系统在展开头文件时,会去 /user/include/ 目录下查找;编译时,会去 /lib64/ 目录下查找。所以,我们只需要将头文件和库函数文件分别拷贝到系统头文件目录和系统库函数目录即可~~
a.直接拷贝
sudo cp ./mymath_lib/include/* /usr/include/ (拷贝头文件)
sudo cp ./mymath_lib/lib/* /usr/local/lib/ (拷贝库函数文件)
b.建立软链接
在 /user/include/ 和 /lib64/ 目录下建立软链接(注:软链接指向的路径必须是绝对路径!!)
给头文件建立软链接(在 /user/include/ 目录下操作):
sudo ln -s /home/common_whb/lesson29/static_warehouse/mymath_lib/include/Add.h Add.h
给库函数文件建立软链接(在 /usr/local/lib/ 目录下操作):
sudo ln -s /home/common_whb/lesson29/static_warehouse/mymath_lib/libmymath.a libmymath.a
值得一提的是,上述两种方法虽然可以让我们在编译源文件时,无需使用指令(-I 和 -L)的方式指明路径,但在编译时,我们仍需使用 -l(小写L,前面那个是大写i)来指明第三方库名。
二.动态库
1.什么是动态库?
--- 本质就是将可能用到的.c文件直接翻译称为.o二进制文件,然后打包成的库,供第三方使用,当第三方使用库内的某一函数时,函数代码会以动态链接的形式被他人使用。
2.打包动态库
把所有的.c文件编译成.o文件的命令:gcc -fPIC -c *.c
把所有的.o文件打包成动态库的命令:gcc -shared -o libmymath.so *.o
把包含库函数的多个.c文件用 gcc -fPIC -c file.c 命令编译相应的.o文件,然后用 gcc -shared -o libmymath.so *.o 命令将所有.o文件打包成 libmymath.so 库
3.动态库的使用方法
接下来,我们依旧站在对方(使用我们提供的静态库的人)的视角,来看看第三方动态库究竟是如何使用的~~
我们会遇到与静态库的使用相似的问题,即:①头文件找不到;②链接报错(库函数文件)
这两个问题的解决方法与静态库一摸一样的,咱们在这里就不再赘述了,直接一行代码搞定~~
gcc test.c -o test -I(大写的i) dynamic_warehouse/mymath_lib/include -L dynamic_warehouse/mymath_lib/lib -l(小写的L) mymath
执行试一试~~
源文件明明已经成功编译成了可执行程序,但运行可执行程序时,为什么还要去找动态库?
哎~~这就要说说静态链接和动态链接的区别了~~
4.静态链接和动态链接
静态链接:链接静态库时,系统会将相关代码和数据直接拷贝到己方源文件中,而后生成可执行程序,这样一来,可执行程序的运行便与静态库再无任何联系。
动态链接:链接动态库时,己方源文件内只会记录调用函数在动态库内的偏移地址,大大减少己方文件代码量的同时,却也提高了可执行程序对动态库的依赖性,即,当我们运行可执行程序时,仍需知道动态库的路径,并将动态库也一并加载到内存!!
gcc默认是动态链接的,但个别库,如果只提供.a(静态库),gcc也没办法,只能局部的把你指定的.a进行静态链接,其他库正常动态链接,而如果命令行有 -static, 无论有无动态库,都会执行静态链接。
所以,当使用静态库的源文件编译形成可执行程序后,可执行程序的运行就和静态库没关系了;而使用动态库编译形成可执行程序后,可执行程序的运行仍需要知道动态库的路径!!
故,由于动态链接生成的可执行程序的运行,仍需知道动态库的路径,所以,用指令(-I 和 -L)的方式编译源文件便不再可行(没法运行程序)了。
5.解决方案
解决方法:①直接拷贝(方法如静态库);②建立软链接(方法如静态库)。这两种方法既能编译源文件形成可执行程序,又能直接运行可执行程序。
而除了上述两种解决方法外,我们还可以用环境变量的方式来添加链接器默认搜索路径上的文件,注意:是链接器(针对的是库函数文件,不包括头文件),不是系统,头文件依旧需要指定路径。
环境变量
使用环境变量LD_LIBRARY_PATH,让系统找到自己的动态库。
虽然LD_LIBRARY_PATH这个环境变量主要用于动态链接器在运行时搜索动态库(.so文件),但在某些情况下,它也可能间接影响链接器在编译时的行为。
操作如下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/common_whb/lesson29/dynamic_warehouse/mymath_lib/lib
即,将包含 .so 文件的目录添加到 LD_LIBRARY_PATH,而不是将 .so 文件本身添加到该环境变量中。
注意:若使用该方法,待xshell外壳程序退出重新登陆时,环境变量会被清除。
小知识:ldd a.out 查看一个可执行程序所链接的动态库
三.动态库加载(底层原理)
1.前置知识
源文件编译成可执行程序的时候,代码被翻译成二进制文件后,原先代码中的变量名和函数名等都变成了一个个逻辑地址,链接动态库时,这些逻辑地址会记录动态库名和库函数的偏移地址,最终形成ELF格式的可执行程序!!
什么是库函数的偏移地址?--- 就是动态库中某一函数相对于该库首地址的偏移量,-fPIC (与位置无关码)的作用,就是让系统用偏移量的方式对库中的函数进行编址。
Linux下,可执行程序是ELF格式的,动态链接的可执行程序,不光程序本身要加载到内存,链接的库也要加载,并且,动态库在加载到内存后,会被所有进程共享!!
ELF格式的可执行文件(二进制文件格式):文件内包含了程序的机器码、数据、符号表、重定位信息以及程序运行所需的其他元数据。
2.动态库加载(首地址+偏移地址)
---源文件能够链接动态库的原因是源文件在翻译的过程中与动态库形成了某种关联(其实就是在可执行程序内记录下了的动态库名和方法偏移量),
由于调用了动态库的源文件形成的可执行程序运行时,仍需要使用动态库,那么,可执行程序是如何使用动态库的呢??
首先,可执行程序在加载到内存后,会在页表中填充虚拟地址和物理地址的映射关系,CPU会读取其表头中的entry代码入口地址,拿到虚拟地址块空间中代码段的入口地址,随后CPU上的指令寄存器便可读取地址空间上的代码,当读取到调用库函数的代码时,OS会查看该动态库是否已经加载到内存(其它进程可能在使用),若其尚未被加载到内存,则加载。
加载动态库的具体细节?
当动态库加载到物理内存后,物理地址与程序地址空间上的虚拟地址通过页表映射,在程序地址空间上的库的首地址会替换库名,通过库首地址,可以在程序地址空间上找到这个库,再通过方法偏移量,可以找到动态库内的某一具体函数。
动态库在被映射到的共享区上的位置不是固定的!!