本章将来学习一下动静态库的相关知识,从认识动静态库到自己手写简单版本的库,最后将库发布出去,如何去使用第三库等方面做详细讲解。目标已经确定,接下来就要搬好小板凳,准备开讲了…
在我们之前学习gcc工具的时候,我们就简单了解过一些关于动静态库的相关知识: 【动静态库复习 - 传送门】
库里面需不需要main函数:
库中是不能带main函数的,因为用户写了main函数,一旦把就出现main函数冲突,所以库里面不要main函数。
如果我把我的所有的. o文件
给别人,别人能链接使用吗?
.o文件
链接形成一个可执行程序吗。
上级目录的源文件(mymath.c和myprint.c)在当前目录下形成目标文件。
再将当前目录下的源文件(test.c)在当前目录下形成目标文件。
我们再讲这三个目标文件链接本地的库生成可执行程序(a.out)。
小结:
.o文件
给别人,别人能连接我的.o文件
来使用,这是可以的。.o文件
,再把这些.o文件
给别人,链接的时候将自己的源文件也变成.o文件
,然后将所有.o文件
一链接就形成可执行了。.o文件
合起来,形成一个可执行程序。要用到的:
上述我们讲了将多个目标文件合起来形成可执行程序。如果包含了几百上千个源文件,在链接的时候容易丢失,所以打包一下就可以了。
ar指令:
在Linux中,“ar” 命令的单词缩写是Archiver"。该命令用于创建和管理静态库(archive) 文件。它通常与其他编译工具(如gcc)一起使用,用于将目标文件打包成一个静态库文件,以供其他程序使用。
所以静态库就相当于,源文件生成.o文件
,最后打包起来就可以了。
当别人想用我的时候,就是在我的这个库里面找对应的.o文件
拷贝到自己的的可执行程序当中。
我们将所有的目标文件归档到静态库中,将库放在一个文件夹里,将所有的头文件打包放在一个文件夹里,整体打包成一个静态库发布出去。
原理与静态库几乎一样,区别就是要用到gcc -fPIC
(产生与位置无关码)。
-fPIC
与地址无关码,将来编译的源代码内部的代码,和将来将这两个.o文件加载到程序任意位置,都可以让程序执行。采用的是起始地址 + 偏移量对的方式,是一种相对地址的方案。动态库的命名:以llib开头 + 名字 + .so
结束。
和上述静态库一样,将头文件和库分别打包最后放在一个文件夹里。
最后为了统一发布,我们将动静态库统一打包:
.PHONY:all
all:libmymath.so libmymath.a
libmymath.so:mymath.o myprint.o
gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o:mymath.c
gcc -fPIC -c mymath.c -o mymath.o -std=c99
myprint.o:myprint.c
gcc -fPIC -c myprint.c -o myprint.o -std=c99
#要注意避免文件名冲突问题
libmymath.a:mymath_s.o myprint_s.o
ar -rc libmymath.a mymath_s.o myprint_s.o
mymath_s.o:mymath.c
gcc -c mymath.c -o mymath_s.o -std=c99
myprint_s.o:myprint.c
gcc -c myprint.c -o myprint_s.o -std=c99
.PHONY:lib
lib:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
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 *.a *.so lib*
这样就能先make
生成各种.o文件
,再make lib
分别生成动静态库。
看一下目录结构:
我们分别将动静态库拷贝到自己的目录中,用自己的代码来试用一下它们。
" "
是在当前路径下找。< >
是在库目录下面找。上述包头文件的方式肯定是不行的:(解决办法)
/usr/include/
路径下,再将自己的库拷贝到系统库文,/usr/lib64/
。(云服务器都是/lib64
)gcc和g++
默认就认识C/C+ +的库,所以不用指定链哪个库。gcc mytest.c -lmymath
-l
指明了,要link什么库。gcc和g++
默认会带上链接系统库,而我们写的是第三方库。光有这个还是不行的,我们还要指明静态库和头文件的搜索路径,正确使用静态库方法:
gcc mytest.c -o mytest -I ./lib-static/include/ -L ./lib-static/lib/ -lmymath
-L
选项后带的是库的路径。-I
选择后带的是头文件的搜索路径。-l
选项带的是库的名字,库名要去掉前面的lib
和后缀.a
。如果将静态库安装到系统的库目录下(一般是/lib64/),就不用上述那么麻烦了, 编译的时候只需要用-l
指定库名即可~
gcc mytest.c -lmymath
静态库的特点:
静态库的特点是库中的实现已经被编译链接进入了可执行程序,即便我们将库给删除,也不影响可执行程序的运行。
动态库和静态库的链接方法基本是一样,既可以将动态库安装到系统目录下,也可以如下方式指定去找:
gcc mytest.c -o mytest -I ./lib-static/include/ -L ./lib-dyl/lib/ -lmymath
用法一样,而且选项的含义也是一样的。
不一样的是运行起来之后:
直接运行发现报错了,报错的内容是:这个错误意味着你的程序无法找到名为 “libmymath.so” 的共享库文件。
使用ldd
命令查看mytest
可执行文件的动态库结构,会发现我们写的动态库是没有找到的:
ldd 是 Linux 系统下的一个命令,用于查看可执行文件或共享库文件所依赖的动态链接库(Dynamic Link Library)。它会列出指定文件所需要的动态链接库及其路径。这个命令在解决程序依赖性问题、查找缺失的库文件或调试系统问题时非常有用。
原因解释:
-I -L -l
是编译器的选项,这些查找属性本身是告诉gcc在哪里找头文件在哪里找对应的lib,找的是什么lib。这些告诉的是gcc,并且形成了可执行程序。/lib64
下,不推荐因为会污染系统库。通过修改环境变量的方式来实现增加动态库的查找路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/Zh_Ser/linux/lesson22/test/lib-dyl/lib
此时环境变量就导入成功啦。
我们再ldd
,和运行一下看看:
ldd命令的结果也显示出了我们自己写的动态库的路径。
但是依旧有个缺点,在Shell重启之后导入的环境变量就不见了,还要重新导入。
通过配置文件来向系统表明要向哪里搜索。
除了修改环境变量,我们还可以修改/etc/ld.so.conf.d
下的文件:
这个路径下表明的是,系统里面如果自定义了动态库,那么此时系统在扫描系统的路径时,除了在系统对应的库路径扫描对应的库之外,还去一个一个的读取这里面的配置文件,根据配置文件里面的内容来找到对应的动态库。
这里的操作非常简单,我们只需要在该目录下新增一个.conf文件
,并在里面写入动态库的绝对路径即可~
sudo touch /etc/ld.so.conf.d/my.conf
sudo vim /etc/ld.so.conf.d/my.conf
sudo ldconfig #子用户权限不够,需要加sudo
ldd mytest
,我们就能看到libmymath.so =>我们自己写的库的路径
。在/lib64
下创建一个软连接来帮我们查找动态库。
sudo ln -s /home/Zh_Ser/linux/lesson22/test/lib-dyl/lib/libmymath.so /lib64/libmymath.so
/lib64/
下创建一个我们自己写的动态库的快捷方式libmymath.so
。动静态库优缺点对比看之前我的博客, 【动静态库复习 - 传送门】。
接下来我们要好好讲讲为什么动态库在程序运行时要找库,而静态库则不需要。
刚刚我们讲了当可执行程序运行起来时,找不到动态库的四个解决办法,但是具体为什么会运行时找不到,我们来讲讲原因。
程序和动态库,是分开加载的:
- 当你运行一个可执行程序时,操作系统会加载可执行程序本身,并为其分配内存空间。然后,动态链接器会在需要的时候加载所需的动态库。
- 动态库是在运行时动态加载的,也就是说,在程序执行过程中,如果有代码调用了动态库中的函数或使用了其中定义的变量,动态链接器才会将相应的动态库加载到进程的内存空间中。动态链接器会解析可执行程序中的动态库依赖关系,并按照依赖关系来加载动态库。这样可以实现动态共享代码和资源的重复利用,减小可执行程序的大小。
- 动态库的加载是延迟的,即只有在需要时才加载。如果某个动态库在程序运行的过程中没有被使用到,那么它也不会被加载到内存中。
- 总结起来,可执行程序在运行时首先加载,然后动态库在需要时被加载,以满足程序对其中定义的函数和变量的调用和访问需求。这种分离加载的机制提供了更高的灵活性和代码复用性。
程序在运行期间可能找不到库的原因是:
所以这个库如果要被加载,就必须要被找到, 所以就倒逼着进程在运行时,必须在运行的时候找对应的库,找不到就报错,进程直接终止。
所以在我们代码区域对库进行调用时,也是在进程地址空间当中进行来回跳转。
动态库
shared libraries
因为可以在运行期间被多个进程所共享,所以叫做共享库。
如果是静态链接,每个进程都有一份代码,就是将库代码编进了自己的代码当中。
优点:
缺点:
动态库中形成的目标文件是加了-fPIC
选项:
-fPIC
是一个编译选项,用于告诉编译器生成位置无关代码(Position Independent Code)。-fPIC
选项的作用是将编译生成的目标文件中的代码和数据访问都使用相对地址,而不是绝对地址。gcc
生成可执行程序时,都要指明库所在的路径。