一、静态库
动态库学习完成后,要学习静态库了。其实,静态库的出现要比动态库要早,毕竟静态库相对动态库来说,从重用的角度来看,可以最先被设计人员想到,早期的静态库,其实就是多个编译单元的简单组合,它完全可以像普通的拼插玩具一样直接拼插到别的软件中,而不需要这个依赖那个依赖。
换句话说,静态库是一个完整的软件模块的描述和运行的集合。既然是完整的,就会有一个现象,那么静态库是可逆向的,或者说,静态库是一系列目标文件的集合的文件。
静态库和动态库的区别在于:
1、静态库是完整的模块,可执行文件直接加载即可使用,而动态库需要进行库目标寻找,动态加载,但是要考虑动态库的依赖关系。
2、静态库链接时只导入必要的符号,而不会加载整个静态库,不过,随着链接的越多,可执行文件会越来越大;动态库链接则需要整体导入,其在链接过程时可执行文件并不会变大(或者说变化非常小),但在运行时,会变大。
在实际情况中,会有这么一种场景,可执行文件需要一个动态库来加载一个静态库,而根据上面的分析,发现二者是互相矛盾的,如何解决这类问题呢?这个在链接器中使用 --whole-archive即可。它表示,无论可执行文件是否需要库中的符号,链接器都要无条件链接之后的N个库。
其它如安卓等都类似的链接符号。
二、创建静态库
在Linux创建静态库非常简单,至少比动态库要简单。创建静态库需要两个步骤:
假设有a.cpp,b.cpp(c文件类似)
1、生成.o文件
g++ -c a.cpp b.cpp
执行完成后在当前目录生成了a.o b.o两个目标文件。
2、使用ar命令打包
ar -rcs libstatictest.a a.o b.o
执行完成后在当前目录会生成libstatictest.a这个静态文件,如果感兴趣可以把这个文件用解压文件解压开,会发现其实就是两个目标文件。
ar命令是一个打包命令,其常用的几个参数意义为:
r:在当前库中插入或者替换模块。
c:创建库,有就替换没有创建。
s:创建目标索引,加快创建库的时间,大S表示不创建。
v:表示显示详细信息,时间、大小等
t:显示库中的具体的文件名称。
其它可参考ar的具体的命令细节。
三、应用
代码走一波:
//add.h
int Add(int a,int b);
//add.cpp
#include <iostream>
#include "add.h"
int Add(int a,int b)
{
return a+b;
}
//mul.h
int Mul(int a,int b);
//mul.cpp
#include <iostream>
#include "mul.h"
int Mul(int a,int b)
{
return a*b;
}
//main.cpp
#include <iostream>
#include "add.h"
#include "mul.h"
int main()
{
int a = Add(1,2);
int b = Mul(2,3);
std::cout<<"Add result:"<<a<<" Mul result:"<< b<<std::endl;
}
编译运行结果:
g++ -c add.cpp mul.cpp
ar -rcs libstest.a add.o mul.o
g++ -o ms main.cpp -L. -lstest ---说明:-o要保持在-lstest前面
Add result:3 Mul result:6
有没有什么问题?对,不像动态库一样需要设置路径,运行时和编译链接时的都得设置,这里只要编译通过了,运行时就可以使用,这也验证了上面的代码膨胀的过程,也就是说,这个库是完整的,不需要在运行时再去寻找加载。
四、总结
在实际工程中,怎么把握使用静态库和动态库呢?这个没有具体的强制性的条目。一般来说,如果库的数量少,想提供给多个支持部门,对可执行文件膨胀大小不太敏感,客户技术水平比较差等情况下,都可以使用静态库。不过,动态库或者说共享库更能体现库的优势,根据实际情况把握就可以了。
在实际工程中,麻烦的在于中间商,要面对各种不同编译情况的动态库,静态库,而且不断的嵌套调用(动态库调动态库,动态库调静态库,依次…),出现的各种离奇的编译链接和运行错误会让程序员崩溃再崩溃。但只要把握好了两者的具体的由来,那么在处理实际问题时,会有一个清晰的思路,指引着解决问题的方向,再根据实际经验不断的调试,就一定能解决大部分问题(有些问题是无法解决的,比如库本身的问题,对方不提供源码且没有支持)。