库有两种:静态库(.a、.lib)和动态库(.so、.dll)。 windows上对应的是.lib、.dll,linux上对应的是.a、.so。
1. 静态库
在链接阶段,将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,此链接方式称为静态链接。
试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成
是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:
a. 静态库对函数库的链接是放在编译时期完成的。
b. 程序在运行时与函数库再无瓜葛,移植方便。
c. 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。如下图,程序1和2都链接了一个数学库。
d. 静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用
户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。
如何编译生成静态库呢?
g++ -c hello.cpp # 将代码文件编译成目标文件.o ar -crv hello.a hello.o # 通过ar工具将目标文件打包成.a静态库文件
Linux下使用静态库,只需要在编译的时候,指定静态库的搜索路径(-L选项)、指定静态库名(不需要lib前缀和.a后缀,使用-l选项)。
2. 动态库
解决静态库的问题我们可以使用共享库(shared library,dll),在运行时使用动态链接器(dynamic linker)与程序进行动态链接来执行。
动态库特点总结:
a. 动态库把对一些库函数的链接载入推迟到程序运行的时期。
b. 可以实现进程之间的资源共享(因此动态库也称为共享库)。
c. 将一些程序升级变得简单。
d. 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
动态链接过程:
1)编译阶段:如果一个符号位于共享库中,那么静态链接的时候链接器会将这个符号引用标记为一个动态链接符号,不对他进行重定位,
而是把重定位的实际留到装载的时候再进行。动态链接器本身也是一个共享库,linux为ld-xx.so。已经加载的动态库由动态链接器记录。
动态链接器和普通的共享对象一样被映射到了进程的地址空间。
2)运行阶段:系统开始运行程序之前,会把控制权给动态链接器,如上图,当运行Program1这个程序的时候,系统首先加载Program1.o,当系统发现Program1.o依
赖Lib.o的时候,那么系统再去加载Lib.o。当所有的目标文件加载完之后,依赖关系也得到了满足,则系统才开始进行链接,这个链接过程和现在链接
非常相似。之前介绍过静态链接的过程,包含符号解析,重定向等。完成这些之后,系统再把控制权交过Program1.o的执行入口,开始执行。如果这个
时候Program2需要运行,则会发现系统中已经存在了Lib.o的副本所以就不需要重新加载Lib.o,直接将Lib.o链接起来就可以了。
如何创建动态库?
g++ -shared -fPIC hello.cpp -o libhello.so
-fPIC作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)。
位置无关代码:CPU取指时,总是相对于本条执行指令的相对地址去取指。比如指行一个ADD指令时,PC要取下一指令的地址,就在原来的基础上+4。
这就不管你代码放在存储器的任何位置,只要他们的相对地址没有改变,就能正常执行程序。
位置相关代码:可以这样来说,就是CPU每次取指都从绝对位置(这里不是指物理地址)去取,而不是上面的相对位置。这个绝对地址就是相对起始地址0来说的。
这样,就要求你每次取指令的时候都是需要做地址重定位的(物理地址+偏移)。
提出共享库概念的目的就是为了最大限度的利用已有代码,充分共享内存中的代码片段。如果使用重定位项修改共享库代码,那么这个库就不能共享了。
归根结底希望程序模块中共享的指令部分在装载时不需要因为装载地址改变而改变。
思路就是把这些指令按照需要被修改的部分剥离处理,需要修改数据部分放在一起,不修改的指令放一起。
这里指令部分就保持不变了,数据部分就可以在每个进程中有一个副本。这就是地址无关代码。