库的本质实际上就是已经写好的,现有的、可以被复用的代码的集合。库被分为两类,一类是静态库:.a[Linux]、.lib[windows];一类是:动态库:.so[Linux]、.dll[Windows]。其实这里就会有疑问,为什么我们平常在编译的时候压根没有使用到库啊,我们只是包含了头文件就可以使用库里的函数了。其实这是因为我们在安装编译器的时候,就自动帮我们安装C/C++的动静态库,并设置了编译器自动寻找动静库的地址,所以我们在编译程序的时候,包含头文件以后就会自动链接C/C++的库,如果我们需要使用自己编写或者不C/C++自带的库的时候,就需要我们进行链接的操作了。
一、静态库
我们都知道程序的编译有四个步骤:预处理、编译、汇编、链接。其实在汇编过程完成之前,我们的程序的每个源文件都是各自进行的,即使某个源文件中使用到了其他源文件中的一些函数,这要在这个源文件中存在外部函数的声明或者头文件,编译过程就不会出错。而编译的最后一步链接就是把汇编形成的多个目标文件(.o)链接在一起形成我们的可执行程序。而静态库的本质其实就是将我们生成的多个目标文件(.o)打一个包!
其实这里应该也会有人疑问为什么要把目标文件打包成静态库,既然静态库是目标文件打一个包形成的,那是不是用压缩程序打一个包也是可以的。
首先第一个问题,一个项目里会存在非常多的模块,每个模块也不可能只存在一个源文件,如果不把目标文件打包成静态库的话,这样在传输和更新的过程中,就需要操作非常多的文件,这样很不方便操作。第二个问题,从本质上来说,静态库(.a)是一种归档文件,本质上和用压缩文件打一个包能达到一样的效果,只不过如果使用压缩文件的这种方式,在使用的时候还需要对压缩文件进行解压,如果打包成静态库的方式,编译器就可以直接进行使用。
1、静态库的制作
首先我们可以使用gcc编译器添加-c选项生产源文件同名的.o文件。
然后使用ar -rc指令生成对应的静态库,这里rc选项表示(replace and create),如果这个静态库需要使用到多个.o文件,在后面添加上即可。这里还需要注意的是静态库的命名规则,静态库的命名需要以lib开头和.a结尾,而中间的那一部分才是静态库真正的名字,在.a的背后还可以加上版本号之类的信息,也就是说这里我生成的静态库的名字其实就是myc。
2、静态库的使用
1.头文件和库文件被安装到系统路径下
这种情况下我们直接使用-l选项指明我们所需要用到的库文件即可(-l选项后可以使用空格分隔)
2.头文件和库文件和我们的源文件在同一路径下
这种情况下我们不仅需要用-l指明库文件,还需要用-L指明库文件的地址,这个地址是绝对路径或者是相对路径都可以。gcc在编译的过程过不仅会在系统路径中寻找头文件,还会在源文件的当前目录下寻找。
3.头文件和库文件都有自己的独立路径
这种情况就需要在上面一种情况的前提下再增加一个-I的选项指明头文件的地址。
这里还有几点需要说明的是,在使用静态的时候,程序是直接把库的代码链接到可执行文件当中去,所以程序在运行的过程中就不再需要静态库了。同时我们在编译程序的时候默认链接的是动态库,只有在该库下找不到动态库(.so)的时候才会采用同名的静态库。如果动静态库同时存在的情况下,我们一定要使用静态库链接的话,在使用gcc编译的时候需要加上-static选项。
二、动态库
动态库是只有在程序的运行的时候才会去链接动态库的代码,多个程序是可以共享使用一个动态库的代码的。源文件在与动态库链接以后,生产的可执行程序仅仅包含它用到的函数入口地址的一个表,二不是外部函数所在目标文件的整个机器代码,因此使用动态库链接生成是可执行文件的大小要更小。在可执行程序运行以前,外部函数的机器码由操作系统从磁盘上的动态库中复制到内存中,这个过程被叫做动态链接(dynamic linking)。因为操作系统采用的是虚拟内存的机制,所以一份物理内存中的动态库代码可以被映射到多份虚拟内存当中,因此物理内存中的这一份代码就可以被多个进程同时使用,即节省了磁盘空间,又节省了内存空间。
1、动态库的生成
动态库的本质其实也是对目标文件进行打包,不同点在于在使用gcc生成目标文件的时候需要加上-fPIC选项,产生位置无关码。
同时生成动态库的时候我们不需要借助其他的程序,gcc本身就可以打包生成动态库,只需要加上-shared选项即可。动态库的命名方式和静态库是一直的,必须以lib开头和.so结尾,中间的才是动态库的名称,在.so的背后可以加上版本号之类的信息。
2、动态库的使用
动态库的使用方式和静态库是一样的,详细可以参考静态库使用的三种方式。
3、库进行搜索路径
这里在使用在使用了静态库一样的方式链接了动态库并生成可执行程序了,同时这个过程并没有报错,我们在执行可执行程序的时候我们发现这里出错了,程序不能正常运行,同时我们使用ldd命令查看这个可执行程序依赖的库文件的时候我们发现,我们自己生成的动态库并没有被找到,所以运行的时候才会出现找不到myc的错误。
这里出现这个错误的原理是我们在编译的时候是把库文件的地址告诉了gcc,gcc知道了我们库文件在哪所以就可以顺利编译出可执行程序。但是这个可执行需要在Linux系统下运行,我们并没有把这个文件的路径告诉系统啊,所以在运行的时候,操作系统会到系统指定的路径下去找这个.so文件,Linux操作系统在指定路径下找不到这个.so文件,所以就出现了上图中的问题。
这个问题就体现出了一个很重要的东西gcc != 操作系统!
解决方案:
1.把.so拷贝到系统共享库的路径下,一般是/usr/lib、/usr/local/lib、 /lib64。
2.在系统共享库路径下建立同名的软连接,在使用这种方式的时候需要使用绝对路径,如下:
3.增加环境变量LD_LIBRARY_PATH,在这个环境变量中添加上库文件的地址,但是这种方式和我们之前说的一样,环境变量都是暂时的,所以在重启bash以后这个环境变量就会消失:
4.在路径/etc/ld.so.conf.d/ 路径下添加以.conf为结尾的文件,只需要在这个文件中添加上库文件的绝对路径,再使用ldconfig重新加载库搜索路径即可:
结论
1、gcc/g++默认使用的是动态库,如果只存在静态库的情况下,可执行要使用这个库文件时,就只能使用静态链接,当静态库和动态库都存在时,如果非要使用静态链接,就要使用-static选项,如果使用了-static选项,就需要要存在对应的静态库。
2、静态库链接的时候是把库文件的代码直接添加到源文件中,这样做的好处是可执行程序在运行的过程中就不需要库文件了,同时这样做导致可执行文件的大小会比较大;动态库链接是只会添加它所用到外部函数的入口地址的表添加到源文件中,这样做的好处是生成的可执行文件会比较小,同时操作系统就只需要加载一份库文件到内存中,就可以被多份源文件使用,即节省了磁盘空间,又节省了内存空间,动态链接就需要告诉操作系统库文件的所在位置。
3、在Linux系统下,默认情况安装的大部分库,默认都优先安装的是动态库。
4、库:应该程序 = 1 : n,也就是说库与应用程序是一对多的关系。