目录
1.静态库与动态库概念
静态库(.a):程序在编译链接的时候把库的代码拷贝到可执行文件中。程序运行的时候将不再需要静态库。动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。注:1.一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。也就是说如果可执行程序是动态链接生成的,程序里面如果涉及到外部函数调用,可执行程序中记录的是外部函数的地址,外部函数具体的实现还在动态库中。2.在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。3.动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
2.动静态库的生成和发布
2.1.文件分离以往的做法
创建一个mklib目录,在该目录下创建静态库。在mklib目录下创建mymath.h、mymath.c文件,mymath.h文件写入下图一所示的代码,mymath.c文件写入下图二所示的代码。
创建一个test.c文件,写入下图三所示的代码,创建makefile文件,写入下图四所示的代码。使用make命令生成可执行程序,然后使用./test执行该程序,运行结果如下图五所示。
注:在mymath.c文件中,我们在for循环体内定义了i变量,如果gcc版本低可能会报错,如下图所示,要解决该问题,我们在makefile文件中生成可执行程序的gcc命令行后面带上-std=c99,如下图二所示,即采用c99标准进行编译。
2.2.静态库的生成和发布
静态库生成:
创建一个mklib目录,在该目录下创建静态库。在mklib目录下创建mymath.h、mymath.c、myprint.h、myprint.c文件,mymath.h文件写入下图一所示的代码,mymath.c文件写入下图二所示的代码,myprint.h文件写入下图三所示的代码,myprint.c文件写入下图四所示的代码。
链接就是把所有的.o文件链接,形成一个可执行程序,如果我们把自己的.o文件给别人,别人也能够链接使用。如果.o文件过多,我们可以将所有的.o文件打包归档,生成一个静态库。生成静态库的命令为ar -rc libmymath.a mymath.o myprint.o ,其中mymath.o和myprint.o为要归档的.o文件,选项rc表示replace and create,libmymath.a为要生成的静态库。
我们在makefile文件中使用ar命令生成静态库。创建makefile文件,写入下图五所示的代码,然后使用make命令生成libmymath.a静态库,如下图六所示。
静态库发布:
我们在使用静态库时,需要静态库文件和头文件,因此在发布的时候需要将静态库文件和头文件打包。
我们创建一个lib-static目录,在lib-static目录中下创建一个lib目录和一个include目录,lib目录下存储静态库文件,include目录下存储头文件。这些操作我们在makefile文件中完成,修改makefile文件代码,如下图一所示,静态库发布目录lib-static的生成如下图二所示。
2.3.动态库的生成和发布
动态库生成:
创建一个mklib目录,在该目录下创建动态库。在mklib目录下创建mymath.h、mymath.c、myprint.h、myprint.c文件,mymath.h文件写入下图一所示的代码,mymath.c文件写入下图二所示的代码,myprint.h文件写入下图三所示的代码,myprint.c文件写入下图四所示的代码。
动态库在形成.o文件的时候需要多一个fPIC选项,fPIC选项表示产生位置无关码。生成动态库的命令为gcc -shared -o libmymath.so mymath.o myprint.o ,其中mymath.o和myprint.o为要归档的.o文件,选项shared表示生成共享库格式,libmymath.so为要生成的动态库。
我们在makefile文件中使用gcc命令生成静态库。创建makefile文件,写入下图五所示的代码,然后使用make命令生成libmymath.so动态库,如下图六所示。
注:生成动态库的命令中fPIC选项解释。之前讲过不加fPIC选项编译出的.o文件,如果我们把这样的.o文件给别人,别人也能够链接使用,这样编译出的.o代码是位置有关码,这样的代码加载到内存不能在内存的任意位置加载,其必须拷贝到自己的main函数程序中,在自己的main函数程序中以绝对地址的方案呈现在进程层面上以供系统调用。加上fPIC选项,编译出的.o文件是位置无关码,这样的代码加载到内存的任意位置,都可以被程序调用,编译出的位置无关码采用的是起始地址加偏移量的方式,是一种相对地址的方案。
动态库发布:
我们在使用动态库时,需要动态库文件和头文件,因此在发布的时候需要将动态库文件和头文件打包。
我们创建一个lib-dyl目录,在lib-dyl目录中下创建一个lib目录和一个include目录,lib目录下存储动态库文件,include目录下存储头文件。这些操作我们在makefile文件中完成,修改makefile文件代码,如下图一所示,动态库发布目录lib-dyl的生成如下图二所示。
补充:同时生成动静态库并发布
创建一个mklib目录,在该目录下创建动静态库。在mklib目录下创建mymath.h、mymath.c、myprint.h、myprint.c文件,mymath.h文件写入下图一所示的代码,mymath.c文件写入下图二所示的代码,myprint.h文件写入下图三所示的代码,myprint.c文件写入下图四所示的代码。
创建makefile文件,写入下图五所示的代码。分别以动静态库生成.o文件的方式生成不同的.o文件,动态库的.o文件命名为mymath.o和myprint.o,静态库的.o文件命名为mymath_s.o和myprint_s.o,然后分别使用gcc和ar命令生成对应的libmymath.so和libmymath.a动静态库。创建一个lib-static目录,在lib-static目录中下创建一个lib目录和一个include目录,lib目录下存储静态库文件,include目录下存储头文件。创建一个lib-dyl目录,在lib-dyl目录中下创建一个lib目录和一个include目录,lib目录下存储动态库文件,include目录下存储头文件。
使用make命令生成对应的动静态库,然后使用make lib命令生成对应的动静态库发布目录,如下图六所示。
3.动静态库的使用
3.1.静态库的使用
补充知识:系统中的头文件都在/usr/include/路径下,如下图一所示,系统中的库文件都在/lib64/路径下,如下图二所示。
静态库的使用:
创建一个test目录,在该目录下使用静态库。将生成的静态库目录lib-static拷贝到test目录下,在test目录下创建mytest.c文件并写入下图一所示的代码。
如下图二所示,mymath.h和myprint.h头文件不在mytest.c文件的当前路径下,这两个头文件在当前路径下的lib-static目录中。包含头文件使用<>符号是在系统头文件路径下查找头文件,使用""符号先在系统头文件路径下查找头文件,后在当前路径下查找头文件,想要包含mymath.h和myprint.h头文件<>和""符号都不行。
解决方法一:将自己的头文件和静态库文件拷贝到系统路径下。
如下图一所示,在root用户下使用命令cp lib-static/include/* /usr/include/将头文件文件拷贝到系统头文件路径下,在root用户下使用命令cp lib-static/lib/* /lib64/将静态库文件拷贝到系统库路径下。像这样将自己的头文件和库文件拷贝到系统路径下的过程叫做库的安装,如下图三所示,像这样将自己的头文件和库文件在系统路径下删除的过程叫做库的卸载。
即使将libmymath.a拷贝到系统库路径下,gcc和g++仍然不认识这个静态库,因为该静态库不是c或c++的标准库,我们编译时需要使用命令gcc mytest.c -lmymath,如下图二所示,在编译的时候将静态库libmymath.a与mytest.c手动链接,其中-lmymath中mymath是静态库libmymath.a真实的名字。
解决方法二:指定头文件和静态库的搜索路径
如下图所示,我们编译时使用命令gcc mytest.c -o mytest -I ./lib-static/include/ -L ./lib-static/lib/ -lmymath,其中-I ./lib-static/include/手动的指定了头文件的搜索路径,-L ./lib-static/lib/手动的指定了静态库的搜索路径,知道了静态库的搜索路径并不知道使用哪个对应的静态库,因此-lmymath将静态库libmymath.a与mytest.c手动链接。
3.2.动态库的使用
创建一个test目录,在该目录下使用动态库。将生成的动态库目录lib-dyl拷贝到test目录下,在test目录下创建mytest.c文件并写入下图一所示的代码。
如下图二所示,mymath.h和myprint.h头文件不在mytest.c文件的当前路径下,这两个头文件在当前路径下的lib-dyl目录中。包含头文件使用<>符号是在系统头文件路径下查找头文件,使用""符号先在系统头文件路径下查找头文件,后在当前路径下查找头文件,想要包含mymath.h和myprint.h头文件<>和""符号都不行。
解决方法有两种,和静态库部分的完全相同,这里不再赘述。这里我们使用前面的第二种解决方法,如下图一所示,使用命令gcc mytest.c -o mytest -I ./lib-dyl/include/ -L ./lib-dyl/lib/ -lmymath进行编译,得到mytest可执行程序。
如果使用的是静态库,编译形成可执行程序后,已经将静态库中所需要的代码拷贝进了我的程序中,因此使用静态库编译形成的可执行程序不依赖静态库,不需要运行时查找。如果使用动态库,编译形成可执行程序后,在程序运行时,需要与动态库进行动态链接,需要运行时查找。
此时运行mytest可执行程序,如下图二所示,提示无法找到libmymath.so动态库,使用ldd mytest命令,可以看到libmymath.so动态库没有找到。前面gcc命令的-L ./lib-dyl/lib/ -lmymath与动态库进行链接只是针对gcc生成mytest可执行程序这个过程,即gcc编译器在编译的时候知道libmymath.so动态库的位置,编译完生成可执行程序,建立进程运行可执行程序时,这个进程并不知道动态库的位置。
如何能够让进程找到动态库呢?有三种解决方法。
解决方法一:将动态库拷贝到/lib64系统路径下(该方法不再赘述)
解决方法二:通过导入环境变量的方式
程序运行的时候,会在环境变量LD_LIBRARY_PATH中查找自己需要的动态库路径,如下图一所示,在环境变量LD_LIBRARY_PATH中用:作为分隔符,使用命令export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dxf/test2023_3_8/mklib/test/lib-dyl/lib/,追加写入自己的libmymath.so动态库路径,此时再使用ldd mytest命令,可以看到libmymath.so动态库找到了,此时./mytest运行程序,如下图二所示,运行成功。
该方法有一个问题,如果我们退出shell然后重新进入,那么我们给环境变量LD_LIBRARY_PATH中追加的路径就没有了,因此mytest可执行程序又运行不了了。
解决方法三:在系统配置文件中添加动态库路径
如果自定义了动态库,系统除了会在/lib64系统路径下查找动态库,还会在/etc/ld.so.conf.d/路径下依次读取其中的配置文件,根据配置文件中的内容找到动态库。/etc/ld.so.conf.d/路径下的配置文件中保存的其实就是路径,如下图一所示,我们使用root用户在该路径下创建一个.conf配置文件,在文件中写入自己的libmymath.so动态库路径,写入内容如下图二所示,然后使用ldconfig命令,该命令是让dxf.conf配置文件生效,也就是说ldconfig命令会将dxf.conf配置文件加载到系统对应的内存空间中,此时在普通用户下ldd mytest可以看到libmymath.so动态库找到了,如下图三所示,此时./mytest运行程序,运行成功。
该方法解决了方法二的问题,即如果我们退出shell然后重新进入,mytest可执行程序依然可以正常运行。
解决方法四:在系统路径下创建动态库的软链接
在root用户下,使用 命令在/lib64系统路径下创建一个动态库的软链接,如下图一所示,此时ldd test可以看到libmymath.so动态库找到了,./mytest运行程序,运行成功,如下图二所示。
问题:为什么使用动态库编译形成可执行程序后,可执行程序的运行还是要依赖动态库?
补充:struct mm_struct里面的相对地址部分,stack栈区从高地址往低地址处申请空间,heap堆区从低地址往高地址处申请空间,二者想向而生,在堆和栈的中间部分还有一块区域,该区域是共享区。
如下图所示,运行磁盘中的可执行程序,程序运行起来形成进程,如果可执行程序使用了动态库,那么进程运行时是需要跳转到动态库中执行代码的,要到动态库中执行代码前提是要将动态库加载到内存中,动态库中代码的地址是与位置无关的地址,因此可以加载到内存的任意位置,然后通过页表将加载到内存中的动态库代码与进程struct mm_struct中堆栈之间的共享区建立映射关系。
建立起映射关系后,进程在执行struct mm_struct中代码区的代码时,如果要用到动态库,则可以直接跳转到共享区执行即可,这样在进程自己的地址空间中,可以执行所有的代码,包括自己的代码和动静态库的代码。
如果此时其他进程也要用到该动态库,不需要再次将磁盘中的动态库加载到内存,直接将前面加载到内存的动态库通过其他进程的页表与其他进程struct mm_struct中堆栈之间的共享区建立映射关系即可。也就是说相同的动态库只加载到内存中一份。如果使用的是静态库,每一个使用静态库的进程其代码中都有静态库代码,那么内存中同一时间可能存在很多个静态库代码。
答:进程运行时,如果要使用动态库,需要通过进程页表与加载到内存中的动态库建立映射关系。这里可以看出,建立映射关系的前提是进程知道动态库在哪,能够找到动态库,这样才能将其加载到内存中使用。
4.安装使用外部库
推荐安装两个Linux下的外部库:
1.ncurses库
介绍:ncurses库是Linux下的图形界面库。
安装:使用yum install -y ncurses-devel命令进行安装
2.boost库
介绍:Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一。
安装:使用yum install -y boost-devel命令进行安装