- 静态库:
实际上所有的编译器都提供一种机制,将所有的相关的目标模块打包成为一个单独的文件。这个文件称为静态库。
静态库可以用作链接器的输入,当链接器构造一个输出的可执行文件时,它只拷贝静态库到被应用程序引用的目标模块。
在编译完成之后,静态库可以被抛弃掉。 - 优点:
可以对文件进行打包。
运行速度较快。
较相对安全。
将编译器的实现与标准函数的实现分离开来,并且仍然对程序员保持适度的便利。 - 缺点:
可能造成生成的目标文件过大。
每个可执行文件都包含着一份引用函数集合的完全拷贝,这对磁盘空间是很浪费的。
如果静态库被修改,则目标文件需要重新进行编译。(更加耗时)
在 linux 中,我们使用 AR 工具进行静态库的创建
在这里我们首先写一个简单的加减乘的函数
然后通过 main.c 文件调用其中的加法和乘法
此时对 main.c 进行编译是无法通过的
现在我们打包出一个静态库
此时可以发现,本地文件中出现了一个 libmymath.a 的静态库文件
通过查看可以发现,相应的 .o 文件已经打包在了动态库文件中。
我们将 main.c 与 libmymath.a 一起进行编译
gcc test.c -I include -L lib -lmymath
//-I + 头文件路径 -L + 库文件路径 -l + 静态库名
此时发现程序可以正常运行了
通过对可执行文件的查看,我们可以发现只插入了 add mul 的函数,而没有插入 sub ,所以只插入需要的内容。
此时就算我们将静态库文件移除,程序依然可以进行,因为相应的程序代码已经装进了代码段。
但是如果我们将第一次对 main.c 进行编译的时候,就需要加上库文件路径 -L +
如果没有自己手动加上路径,系统会先在 /lib /lib64 . 文件中寻找相应的静态库文件
如果没有找到则报错。 所以我们也可以将自己打包的静态库文件放到 /lib 或 /lib64 中。
现在整个工作过程已经熟悉了,我们来了解连接器是如何使用静态库来解析引用的。
在符号解析的阶段,链接器从左到右按照他们在编译器驱动程序命令行上出现的顺序来扫描可可重定位文件和存档文件。
在这次扫描中,连接器维持一个可重定位目标文件的集合 E,一个未解析符号集合 U,一个在前面输入文件已定义的符号集合 D。初始 E,U,D都为空。
- 对于命令行上的每个输入文件 f ,链接器会判断 f 是一个目标文件还是一个存档文件,如果是一个目标文件,那么链接器把 f 添加到 E 中,修改 U 和 D 来反映 f 的符号定义和引用,并继续读取下一个输入文件。
- 如果 f 是一个存档文件,那么链接器就尝试匹配 U 中未解析的符号和由存档文件成员定义的符号。如果某个存档文件成员 m,定义了一个符号来解析 U 中的一个引用,那么就将 m 加到 E 中,并且链接器修改 U 和 D 来反映 m 中的符号定义和引用。对存档文件所有成员目标文件都反复进行这个过程,直到 U 和 D 不再繁盛变化。
- 如果当链接器完成对命令行上输入文件的扫描后,U 是非空的,那么链接器就会输出一个错误并终止。否则它会合并重定位 E 中的目标文件,从而构建输出的可执行文件。
注意:命令行上的库和目标文件的顺序非常重要,如果定义一个符号的库出现在引用这个符号的目标文件之前,那么引用就不能被解析,链接会失败。并且如果库不是相互独立的,那么他们同样必须排序。如果需要满足依赖需求,可以在命令行上重复库。