文章目录
- 回顾基础
- 学会打包自己的库并使用
- 静态库
- 打包库
- 第三方库的使用
- 动态库
- 打包库
- 第三方库的使用
- 动态库加载以及周边问题
回顾基础
这篇文章主要对动静态库进行进一步的学习,关于动静态库的一些基础知识,请点击这篇文章的链接:【Linux】初识动静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
1.我们在vs编译器下安装开发环境——本质就是安装编译器软件,安装要开发的语言配套的库和头文件。
2.我们在使用编译器的时候,都会有语法的自动提醒功能,有这个提醒功能的前提是要先包含对应的头文件的,所以语法提醒的本质就是编译器或者编辑器,会自动根据用户输入的内容,不断的在被包含的头文件中进行搜索,自动提醒功能是依赖头文件的。
3.系统已经预装了C/C++的头文件和库文件,头文件提供方法说明,库才提供方法实现,头和库是有对应关系的,是要组合在一起使用的。
4.头文件是在预处理的阶段就引入的,链接的本质实际是在链接库。
学会打包自己的库并使用
静态库
打包库
这里我们用一个简单的加减函数来示范,我们先创建以下几个文件,并在mysub,myadd文件中实现对应的功能。(简单的加减函数这里就不赘述了)
当别入需要使用我们实现的代码功能时,我们不可能把myadd.c,mysub.c源代码直接交出去,因为这就暴露了我们代码的实现细节,所以我们往往将.c .h
文件打包成一个库(二进制文件),再给别人。
所以我们这里又创建了一个目录otherPerson代表需要用我们打包的库的其他人。
开始演示:
原因:当我们有了一个库,接下来,要将库引入我们的项目,必须让编译器找到头文件+库文件。
看!现在“别人”通过库文件和头文件就可以成功运行程序了。
步骤:
- 先准备好
.h和.c
文件,然后用gcc
的-c
选项生成.o
目标文件; - 命令
ar -rc libmymath.a *.o
生成静态库,ar是gnu归档工具,rc表示(replace and create); - 查看静态库的目录列表:
ar -tv libmymath.a
,t:列出静态库中的文件,v:详细信息; - 运行程序命令:
gcc -o mytest main.c -L. -lmymath
,-L:指定库路径,-l:指定库名。 - 测试目标文件生成后,静态库删掉,程序照样可以运行。
但实际上一般是打包头文件和库文件给其他人的:
第三方库的使用
1.需要指定的头文件和库文件;
2. 如果没有默认安装到系统gcc,g++默认的搜索路径下,用户必须指明对应的选项,告知编译器头文件在哪里,库文件在哪里,库文件具体是谁。
3. 将我们下载下来的库和头文件,拷贝到系统默认路径下——这个行为就是在Linux下安装库。对任何软件而言,安装和卸载的本质就是拷贝到系统的特定路径。
4. 如果我们按安装的是第三方的(1,2语言,操作系统接口)库,我们要正常使用,即便是已经全部安装到了系统中,gcc/g++必须用-l
指明具体库的名称。
5. 无论是从网络中直接下好的库,或者源代码(可能会提供内置编译方法)——必须有make install安装的命令,实际上就是拷贝安装到系统中,所以我们安装大部分命令,库等等,都是需要sudo或者超级用户操作(因为需要访问系统)。
将第三方库安装到系统中:(还是以上述为例子)
a.提取权限将我们include目录下的所有头文件拷贝到系统头文件/usr/include/
中:
sudo cp -rf include/* /usr/include/
b.提取权限将我们lib目录下的静态库拷贝到系统的库/lib64
中:
sudo cp lib/* /lib64
c.这时就算我们删除了lib和include目录,只有main.c文件,也能成功生成可执行程序,正确编译:
gcc -o mytest main.c -lmymath
云服务器一般都只会提供动态库,下面是安装静态库的命令:
sudo yum install -y glibc-static
sudo yum install -y libstdc++-static
动态库
打包库
还是以上面的加减函数为例:
编译生成.o
目标文件:
fPIC:产生位置无关码(position independent code)
进行打包,动态库的打包就不是用ar了,而是还是用gcc,用法如下:
shared: 表示生成共享库格式
将.o
文件打包成动态库:
还是一样的准备两个自己的目录:include和lib,分别装填自己的头文件和动态库。
将头文件include和lib库文件打包:
第三方库的使用
将mylib中我们已创建好的包mymath.tgz传给“其他人”,本质是拷贝到指定目录下的一个过程。
报的错是找不到动态库,但是我们已经采用上面讲到过的方法,告诉了编译器头文件的位置-I
和库的位置-L
,还有文件名-l
,但是竟然没有成功!为什么呢?
那是因为,运行的时候.so
文件并没有在系统的默认路径下,所以操作系统依旧找不到,静态库能找到的原因是,静态库的链接原则是将用户使用的二进制代码拷贝到了目标可执行程序中,但是动态库的链接原则不是这样的。
那么运行时,OS是如何查找动态库?方法如下:
方法一:配置环境变量
环境变量:LD_LIBRARY_PATH(临时方案,因为环境变量只在当前会话有效,退出了就没了)
echo $LD_LIBRARY_PATH //查看环境变量
方法二:软链接方案
我们在/lib64
下建立了一个软链接,指向libmymath.so
目标文件的所在路径。
方法三:配置文件方案
1.所要配置的文件名称:/etc/ld.so.conf.d/
2.获取权限在该配置文件下创建一个你的文件,如:sudo touch /etc/ld.so.conf.d/nan_test.conf
3.查看是否创建成功:ls /etc/ld.so.conf.d/
4.查看所需第三方动态库所在路径,以上述例子为例,lib动态库的路径是:
/home/nan/linux-learning/基础IO/动态库/otherPerson/lib
5.获取权限vim打开你刚刚在配置文件下创建的文件:sudo vim /etc/ld.so.conf.d/nan_test.conf
将你刚刚获得的动态库路径添加进去,并保存。
6.ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新:sudo ldconfig
7.查看动态库是否添加成功:ldd mytest(可执行程序)
8.最后执行程序,over!!!
动态库加载以及周边问题
静态链接的加载:静态链接形成的可执行程序,本身就有静态库中对应方法的实现,但是非常占用资源!(可执行程序变大加载占用内存,下载周期变长,占用网络资源)
动态链接的加载:将可执行程序中的外部符号,替换成为库中的具体地址。只要我们把库加载到内存,映射到进程的虚拟地址空间之后,我们的代码执行库中的方法,在自己的地址空间内进行函数跳转即可。
库中地址的理解
动态库面临一个问题:不同的进程,运行程度不同,需要使用的第三方库是不同的,每一个进程的共享空间(栈和堆之间有一块共享区)中空闲位置是不确定的!那么动态库和静态库加载的地址是怎么处理的呢?
答:由编译器确定,编译器确定地址分为两种形式:
1.根据当前PC+偏移量确定地址(动态库采用)
2.直接用链接地址(通过链接脚本指定)(静态库采用)
区别就是:代码运行的时候,是否和所处内存地址有关。
位置有关码要求运行是代码必须在特定位置;
位置无关码则没有要求,动态库中的所有地址,都是相对于当前PC值的偏移量,默认地址从0地址开始。动态库,在进程的地址空间中,随便加载,与我们加载到地址空间的什么位置,就毫无关系了,动态库中地址,都是偏移量。
这也就是为什么,上面我们在制作动态库的时候,要加上-fPIC选项:
位置无关码和位置有关码可以用下面一个生活的例子感性理解一下:
1.我在这条路的60米处左右——绝对编址
2.我在这条路的一棵大树左边,大约距离20米——相对编址