目录
一.制作和使用静态库
1.制作静态库
库中的方法将方法编译形成.o文件,方法声明放在头文件。用户如果想要使用库中的方法,只需在自己写的源文件中#include提供的头文件,编译自己写的源文件形成.o文件,再和提供的.o文件链接,形成可执行程序。
例如main.c是我自己写的源文件,其它头文件和.o文件是别人提供的,我想使用别人的写的方法,只在main.c中包含给的头文件,编译成.o文件,再和别人的.o文件链接形成可执行文件。
但是这样写真的很麻烦,如果有多个目标文件,链接时每个都要写出来效率也太慢了。因此将所有.o文件打包起来,形成库文件。
静态库原理:
静态库就是将库中的源代码编译成为.o目标二进制文件,然后打包
制作过程:
编译源文件形成目标文件,然后将目标文件打包形成.a静态库
2.使用静态库
(1)编译时链接库的gcc指令
当前目录下只有我写的源文件main.c,对它编译报错,gcc找不到add.h这个头文件。所以我们应该将头文件和库都放在当前目录下。
将头文件和库文件都拷贝到当前目录下,编译还是报错:找不到方法的定义,说明gcc并没有链接mymath。编译器不认识mymath这个库,因为它是第三方库。对于这种第三方库,需要我们编译时指定名称。
gcc main.c -l mymath -L . //-l 指定库的名称,库的名称要去掉前缀lib,去掉后缀.a
//-L 指定库的路径,对于静态库,即使放在当前路径下也要指定路径
(2)第一二三方库
- 第一方库指写操作系统的程序员写的库
- 第二方库指C语言的库clib
- 第三方库是其它程序员写的库
- 第一方二方库gcc是认识的,所以我们之前编译时从来没有使用过-l -L这样的选项。但是第三方库gcc是不认识的,所以我们必须用-l指明要链接哪个库,必要时用-L指明库的路径,比如静态库放在当前路径下也要指明路径。
(3)ldd指令
ldd a.out //ldd用于查询可执行程序依赖的库(动态库)
查询结果并没有mymath,因为mymath是静态库, 编译时就已经将代码拷贝到可执行程序中了。ldd中的d是“dynamic“,只能查询静态库
(4)编译链接时加不加-static的区别
不带-static,有动态库就链接动态库,如果某个库只提供了静态库,就链接静态库。->尽量链接动态库
带-static,所有库必须都静态链接,如果没有提供静态库就报错 ->只链接静态库
(5)整合头文件和库文件
将头文件和库文件都放在用户自己写的源文件下显得很乱,用户也容易误删,所以应该将头文件和库文件归类放在相应目录下
(6)头文件不在当前目录下时的编译方法
方法一:更改源文件,include头文件时指明路径(非常不推荐)
方法二:编译时加上-I(i的大写!!!)选项,增加头文件搜索路径
gcc -I mymath_lib/include -l mymath -L mymath_lib/lib
//-I指明新增的头文件搜索路径,即在系统默认搜索路径/usr/include,当前路径和mymath_lib/include路径下搜索头文件
方法三:将头文件拷贝到系统默认搜索路径下
编译时就不需要带上-I选项
同理,库文件也可以安装到默认搜索路径
编译时就就不需要带上-L选项了
二.制作和使用静态库
1.制作动态库
静态库原理和动态库原理一样,先形成.o文件,然后打包所有.o文件。
动态库比静态库更加常用,gcc编译器带有制作动态库的方法,不需要借助其它工具
-fPIC ——产生位置无关码
-shared——形成共享库(动态库)
2.使用动态库
(1)编译链接
编译时链接动态库和静态库的方法是一样的,都是-I -l -L三个选项
(2)运行可执行程序
运行可执行程序必须同时找到可执行程序和依赖的动态库,程序和动态库都要加载到内存。
系统只会在当前路径和库的默认搜索路径下查找,所以必须让系统找到库在哪里
方法一:将库文件拷贝到/lib64目录下
方法二:在当前目录或者系统的默认搜索路径下建立所依赖库的软链接(注意软链接的名字和库文件名字相同)
方法三:更改环境变量LD_LIBRARY_PATH,使系统找到动态库(想要永久保存,需要更改系统登录时导入环境变量的配置文件)
方法四:更改系统关于动态库的配置文件,将库所在路径写入配置文件,写完后刷新ldconfig
三.动态库加载
1.可执行程序的地址
磁盘上的可执行程序在加载到内存之前内部也是有地址的。我们在编写代码时使用的是变量名,函数名等符号,但编译完的代码使用的都不是这些符号,而是代码和数据的地址,例如访问某个变量就是访问某个地址的空间,调用某个函数是跳转到函数的起始地址去执行。这些地址的编排规则和虚拟地址空间是一致的。
所以可执行程序也是分为代码段,数据段,栈区,堆区等区域,此外还多了一个符号表,里面记录了所有符号(全局变量,函数)对应的地址。
正是因为可执行程序的地址编排和遵守虚拟地址空间那一套规则,所以将磁盘上的文件加载到内存,操作系统为进程安排地址空间,建立页表映射时就省去不少麻烦。
2.绝对编址与相对编址
可执行程序采用绝对编址的方式安排地址,即00000000~FFFFFFFF,动态库采用相对编址,即相对于库的起始地址的偏移量。
因为库是不会单独加载到内存运行的,它是供其它进程访问的,所以并不需要将它按照虚拟地址空间的方式编址
由于动态库代码在编译时期并没有合并到可执行程序内部,可执行程序内仅仅记录了要调用的方法在哪个库的多少偏移量处。进程运行时,它所依赖的动态库也必须加载到内存,库被加载到内存后,它的起始地址也就能确定了,起始地址加上编译时期记录的偏移量,就能确定库方法的地址了。
由于动态库内的地址是相对地址,它的起始地址是在加载到内存后才确定的,所以动态库被加载到不同的位置没有关系,这种通过相对编址方式编出来的地址就叫做位置无关码
没有加载静态库这一说,因为在链接阶段,静态库的代码就被拷贝到可执行程序的代码段了,会统一进行绝对编地址,以后再加载时和静态库没有任何关系了。可以将静态库就看成你写的代码,只不过你写的代码还要从.c编译成.o才能链接,而静态库省去了编译这一步骤。
3.程序和库的加载过程
库被加载到内存之后,要被映射到指定使用了该库的进程们的地址空间的共享区部分,库在共享区的位置是任意的,但是一旦建立映射关系后,库的起始地址就确定了,操作系统会将这个起始地址记录下来,起始地址+偏移量就能找到库方法。
4.调用自己的函数vs调用库函数
自己的函数在代码段,库函数在共享段,调用自己的函数在代码段内跳转,调用库函数在代码段和共享区之间跳转,但是二者都是在进程的虚拟地址空间内跳转。
5.共享库
在你启动某个进程之前,进程依赖的某个动态库可能已经在内存里了,因为其它运行的进程也有可能依赖这个库。此时操作系统无需重复加载该库,只需为新启动的进程,建立虚拟地址空间的共享区和物理内存的映射关系即可。动态库是被多个进程共享的,所以动态库也叫作共享库。
顺便解释一下,静态库是指库代码相对于程序其它代码的位置是固定不变的,永远都在代码段的某个固定位置,动态库指库代码在可以加载到共享区的任意位置