概述:
库文件一般就是编译好的二进制文件,用于在链接阶段同目标代码一块生成可执行文件,或者运行可执行文件的时候被加载,以遍调用库文件中的某段代码。
动态链接都是索引的.so文件,静态链接都是压缩打包的.a文件。
库文件和可执行文件相比:
- 相同点:两者都是编译好的二进制文件
- 不同点:库文件没法直接执行(直观上看,他的源代码中没有main函数,而只是一些函数模块的定义和实现,没有运行的入口主函数,所以库文件没法直接执行)。
我们的程序开发,无论是运行的时候,还是编译,链接的时候,一般都需要借助一些库来实现他们的功能,而很少直接通过程序的源代码生成完全独立的可执行文件。
静态库:
静态库概述:
静态库实际上就是一些目标文件的(一般是以.o结尾)的集合,静态库一般是以.a结尾,只用于链接生成可执行文件阶段。
具体来说,以c程序为例子,一般我们编译程序源代码的时候过程大致是:
- 以.c为后缀的源文件经过编译生成.o文件的目标文件
- 以.o为后缀的目标文件经过链接生成最终的可执行文件。
我们可以在链接的时候直接链接.o的目标文件,也可以将这些.o文件打包集中起来,统一链接,而生成的打包集成了所有.o文件,也就是静态库。
静态库只在程序连接的时候时候,连接器会将程序中使用到函数的代码从库文件中拷贝到应用程序中,一旦链接完成生成可执行文件之后,执行程序的时候就不再需要静态库了。
由于每一个静态库的应用程序都是需要拷贝所用到的函数的代码,所以静态库链接生成可执行文件会比较大,多个程序运行的时候占用的内存空间比较大,(每个程序在内存中都有一份复制的静态库代码),但是由于运行的时候不用从外部动态加载额外的库了,速度会比共享库快。
静态库的制作
实例:假设在实际工作中设计了两个算法,但是需要卖给其他公司使用,为了保护公司的知识产权是不能将源代码交给其他公司的,这样我们可以将两个算法封装或者打包到一个静态库中。
假设实现这两个算法的函数为func1()和func()2。
新建一个头文件hello.h,在hello.h中对两个函数进行声明:
编写一个简单的程序“hello.c”
将hello.c编译生成.o文件
gcc -c hello.c -o hello.o
使用ar命令(ar是archive的意思)将hello.o文件打包成一个静态库文件libhello.a
ar rcs libhello.a hello.o
注意:
- 如果需要将多个.o文件打包成文件库,则在命令行后边填写所有.o文件的名字即可
- 生成的库的名字必须是libxxx.a格式(注意:这里是尽量最好使用lib开头的格式,因为其他格式的编译方式是不一样的)
编译参数:<rcs>
r:表明将模块加入到静态库中,c表示创建静态库,s表示产生索引
使用nm命令查看库文件中包含的符号:nm libhello.a
使用 ar -t 命令查看静态库是由那些.o文件组成的:ar -t libhello.a
编写一个带有主函数的程序main.c,并且在main.c中调用静态库中的func1和func2函数:
现在所有文件下都在同一个目录下,没法直接体现出来静态库的作用。
在IO_file_test目录下新建一个static_lib_test目录进行测试将文件移动到下边设备树结构。(这里是一个比较常用的目录结构,但是为了方便测试三个文件都放在一个文件目录下即可)
解释:
include目录下是.h文件
lib里边是链接的静态库文件
src里边是.c文件
编译:
gcc main.c -L../lib -lhello -I../include -o main
编译解释:
-L指向的是链接的静态库路径
-l(小L)是静态库的命称(去掉lib之后的)
-I(大i)链接存放.h文件的路径
编译现象:
在静态库的概述中会标红的地方提到了(一旦链接完成生成可执行文件之后,执行程序的时候就不再需要静态库了),所以这时删除掉libhello.a文件后程序仍然可以执行。
共享库(动态库)
共享库概述
共享库也叫做动态库。
静态库缺点:
- 内存消耗大;(假设一个静态库的大小为1M,当两千个程序都用到这库将消耗掉2G内存)
- 当静态库中的代码优化的时候,所有调用该静态库的程序需要重新编译,这样太过与麻烦。
动态库特点:
- 动态库在程序编译的时候并不会被链接到目标代码当中,而是在程序运行的时候才被载入。
- 不同的应用程序如果调用相同的库,那么在内存中里只需要有一份共享库的实例,规避了空间浪费问题。
- 动态库在程序运行的时候才能被载入,也解决了静态库对程序的更新,部署和发布页带来的麻烦。用户只需要更新动态库即可,增量更新。
动态库制作
参照静态库目录下创建三个文件如下:内容和静态库的也相同
编译链接生成.o文件
将编译好生成好的hello.o编译编译生成动态库:gcc -shared -fPIC -o libhello.so hello.o
注意:
- -shared:表明是使用的共享库
- -fPIC:表明生成与位置无关的代码、
- 动态库后缀为.so
- 动态库格式libxxx.so
- 如果还有其他链接的.o文件,直接在hello.o后边加上,用空格隔开
使用nm命令查看一个库文件中包含的符号: nm libhello.so
对照静态库测试目录制作动态库目录格式:
将上边共享库目录的文件对应拷贝到共享库测试目录里边如下:
进行编译:gcc main.c -L../lib -lhello -I../include -o main
但是如果直接执行的话,编译器会爆出一个错误。
错误的主要意思是在加载的时候打不开动态库文件,但是注意:动态库在在程序执行的时候会被加载。
这是因为在Linux中启动一个ELF格式的二进制可执行文件会自动启动和运行一个program loader(程序加载器:用来加载一些库)。对于Linux系统,这个loader的名字是ld-linux.so.X(X是版本号)。这个loader启动后会load所有的其他本程序的要使用的共享函数库。动态库与静态库不同,在执行文件的阶段需要加载动态库文件,而加载的动态库文件需要在指定位置或者添加在环境变量中,才能正确的加载动态库文件。
动态链接库的查找先后顺序:
- LD_LIBRARY_PATH环境变量中的路径
- /etc/ld.so.cache缓存文件
- /usr/lib和/lib
动态库临时生效
可以临时将libhello.so所在的路径追加到环境变量LD_LIBRARY_PATH中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/book/Desktop/IO_file_test/shared_lib_test/lib
冒号后边跟是自己的动态库路径。
修改完成之后我们可以使用echo $LD_LIBRARY_PATH查看环境变量被修改的值
注意: 在终端使用expert命令来修改当前环境变量的值,只是对当前会话临时生效,如果切换到一个新的会话窗口或者新的终端,关闭当前会话窗口都会失效。
动态库长期生效
- 修改系统启动配置文件,将export命令添加到启动文件当中
设置环境变量:
1.打开 vim ~/.bashrc 文件
2.在文件末尾添加一行
export LIBRARY_PATH=$LIBRARY_PATH:库文件的路径(如果需要删除环境变量,需要在~/.bashrc中删除环境变量后,关掉终端,重新启动终端才能生效)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/book/Desktop/IO_file_test/shared_lib_test/lib
3.重新加载配置文件 source ~/.bashrc
- 将动态库文件拷贝到/usr/lib或者/lib目录下(一般不采用这个种方式,因为这样会污染系统,并且不方便项目迁移)
- 修改/etc/ld.so.conf文件
动态库的升级
引入:
在实际工作当中,我们可能经常需要对动态库的板本进行升级,假如原来的库为libhello.so,那么升级完以后还需要保留每一个旧的板本,动态库的名字就不能重复,但是程序在加载的时候只加载libhello.so文件,解决:所以我们可以对每一个板本的动态库文件进行不同的命名,然后在新建立软链接,链接需要使用的实体库即可。
根据动态的命名规则,我们可以将动态库的第一个板本命名为:libhello.so.1.0.0,然后创建软连接
ln -s libhello.so.1.0.0 libhello.so
假设我们在原来的库上增加了某些接口我们可以将新库的版本名命名为libhello.so.1.1.0,删除原来的软连接文件,并重新创建软链接文件到该文件。
位置无关代码
位置无关代码(PIC:position-independent code),也被称为地址无关代码,是指源代码被编译成二进制文件(可以是执行文件也可以是某个库文件),编码方式也和位置(地址)无关。
程序在运行的时候是需要被加载到物理内存上运行的,但是操作系统为了方便每个进程进行内存管理,使用虚拟内存技术,每一个进程运行时都会得到4G的虚拟内存。这个虚拟内存可以认为每个进程任务自己认为自己有4G的空间,着只是每个进程认为的,但是实际上,在虚拟内存对应的物理内存上,可能只对应一点点的物理内存,实际用了多少内存,就会对应多少物理内存。
使用静态库和动态库编译生成的可执行文件在运行时候在内存上的加载方式:
如果使用了静态库编译可执行程序,每个函数在内存中的位置(地址)在编译完成以后就已经决定了。
但是共享库(动态库)是在可执行程序执行的时候加载进去的,动态编译后的函数的调用地址是自动计算出来的,这就是与位置无关的代码。
下边是找到的一个资料对位置无关代码的验证:
在64为机器上可以运行32位的指令。