什么是库
首先创建三个文件:add.h add.c main.c
这一步将add.c和main.c进行了汇编,就差链接了,add.o和main.o里面都是二进制。
既然是二进制,那么我们肯定是看不懂的,这个时候就可以让别人也来使用add这个方法了,只需要将add.o和add.h给他就好,头文件是有哪些方法,源文件是方法具体怎么实现的。
但是如果.o的文件太多了怎么办?那么我们可以将.o的文件尝试打包,给对方提供一个库文件即可。
所以,库就是多个.o文件打包成一个文件,然后分为静态库和动态库。库的本质就是多个.o文件的集合
库的使用
静态库
那么我们如何来打包呢?
add.c是加法实现方法,sub.c是减法实现方法。
用ar -rc选项去进行打包。
这时就形成了一个库文件。
这里要注意:交付一个库,就等于将头文件和库文件一起交付,不然会报错。
这里进行给库打包然后放在别的目录看看效果:
然后将test这个目录打包,然后放在其他目录:
然后将main.c放在同目录下。
我们知道,现在还不能编译,因为现在头文件和库没有在同一目录下:
编译器默认搜索头文件:
1.在同一路经下。2.在/usr/lib中。
编译器默认搜索库:
1.在/usr/lib中。2.指定路径下。(注意如果要链接第三方库,就算是在/usr/lib中,必须要有库的名称,库的名称是去掉前缀去掉后缀)
那么为什么我们之前从来没写过库的名字呢?因为,gcc和g++认识他们自带的库,所以不用带库名称。
格式:选项 -I 是指定头文件目录,选项 -L 指定库文件路径,选项 -l 库文件名。
刚才我们使用的是静态库,但是我们查看以后发现:
一没显示我们自己写的库,二显示用的是动态库,这是怎么回事呢?
首先gcc默认是动态链接,然后程序不仅仅只链接一个库,静态库和动态库都有。
gcc只是建议使用动态库,但是具体你想用静态还是动态取决于提供的是动态库和静态库。并且,只要有一个动态库,那么就要用动态链接!
显示动态库是因为默认用的是C的库,所以gcc只能把静态库拷贝到代码里面。
指令这么长,写的也确实很难受,如果有几百个第三方库,就更难受了,所以这里提供一个减少指令行的方法。
放在/usr/lib中。(第三方库的名称一定要带)
这里就不演示了,也不推荐放在里面,因为我们是用来测试一次或者是两次,删除的时候容易删除掉重要文件!
动态库
和动态库的步骤没啥区别,就是gcc多加了选项。
-fPIC: 产生位置无关码
shared: 表示生成共享库格式
然后我们来执行:
这里发现报错了,原因是没有发现这个目录或文件。
为什么找不到呢?
首先要考虑清除一件事:用户告诉了库文件的路径和库名,我们是告诉了谁?
我们是告诉了gcc,但是gcc编译完之后就不管了,形成可执行文件执行是系统的事情!库没有在系统的路径下!
所以系统和shell也要知道在哪里。
除了会在系统路径下,还会再一个环境变量去找。
LD_LIBRARY_PATH
这时运行成功了。
但如果我们想永久设置动态库搜索路径,这种方法是行不通的。
在这个路径有配置文件:
/etc/ld.so.conf.d/
这些配置文件是动态库在进行搜索时,自己定义配置conf文件方式,让操作系统找到动态库。
自己创建一个配置文件然后再里面写入动态库的路径:
记得提权输入
然后wq!退出,之后要刷新一下配置文件才行。
ldconfig
这个也要提权才行。
然后回到原来路径执行命令可执行程序就能通过了。
除此之外,还有一种方式就是软链接的方式:
这说明搜索库的时候会在当前路径下搜索。
如果不想再当前路径下搞软链接,也可以再/lib64路径下进行软链接,也可以将这个库拷贝到/usr/lib路径下。
那么如何使用别人的第三方库呢?
只要下载好了,用gcc只需要告诉库名字即可。
动态库加载
静态库是怎么加入代码中的
静态库的代码是拷贝在代码区。未来这部分代码通过相对确定的地址位置来进行访问。
静态库在程序进入内存之前就被拷贝进了我们的程序内部,进入内存,在虚拟地址空间中也是在代码区里面,这样是很浪费空间的。
静态库也是采用绝对编址的方式,假如说要使用某个函数,那么这个函数在整个程序中的位置必须确定。
动态库如何加载到内存
动态库是指定函数的地址写入可执行程序中。这个地址是什么地址呢?
之前gcc选项编译动态库的时候有一个产生位置无关码:fPIC。这种采用的是相对编址。
这个就相当于是一个坐标,以这个坐标为基准,某个人或物距离这个坐标多远,是这么用的。
相当于一个start+偏移量。
start相当于动态库的起始地址,偏移量就是用户要用的函数在这个库当中的位置。
那么写入的地址就是那个偏移量!
然后代码在运行的时候发现到了一个库函数,然后操作系统回去看内存中库函数定义的代码发现这个并不存在,地址也是一个外部地址,所以就回到了磁盘当中找动态库,然后加载到内存中,然后通过页表映射到共享区:
这就是为什么我们用ldd的时候他知道我们链接的哪个库。
一旦映射到共享区,立刻就能够决定这个库的起始地址。
上面步骤执行完之后,操作系统才会继续读取代码。
回到调用库函数的地方,然后再虚地址空间发现库函数的偏移量,然后去共享区的库找到该函数代码的位置。
如果有很多个进程,那么内存当中也只需要加载这一个库就够了,因为每个进程操作系统都会帮助我们去映射对应库再虚拟地址空间的位置。