通俗意义上讲,静态库就是Linux系统中的”XXX.a“文件,它实际上是一组目标文件的集合(”XXX.o"),多个目标文件经过打包,就得到了静态库。我们都知道,一个.cpp文件需要经过预编译(包括去掉#define并将其替换,处理条件编译如#if、#ifdef,删掉注释,添加行号,保留#pragma指令)得到.ii文件;编译(将c++代码翻译成汇编代码)得到.s文件;汇编(将汇编代码翻译成机器指令)得到目标文件即.o文件;链接(-lXXX链接一些库)得到可执行文件(一般来说是a.out),之后就可以执行了。
示例:
首先有一个greet.cpp文件:
//
// Created by wang66 on 2020/5/24.
// greet.cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
vector<string> names={
"Bob","Andy","Harry"
};
for(int i=0;i<names.size();++i){
cout<<"hello!"<<names[i]<<"!"<<endl;
}
}
预编译(-std=c++11是因为我用了c++11语法,不加可能会报错):
g++ -std=c++11 -E greet.cpp -o greet.ii
可以利用:
vim greet.ii
来查看一下生成的文件,这里面用了模板,所以这个生成的文件相当大。
接下来编译:
g++ -std=c++11 -S greet.ii -o greet.s
得到汇编文件。紧接着用
g++ -std=c++11 -c greet.ii -o greet.o
得到目标文件。这个时候其实需要链接器ld来处理了,但我们不会处理它,因为会相当繁琐,其实这个东西可以一步到位的:
g++ -std=c++11 greet.cpp -o greet
让g++程序一次性完成预编译、编译、汇编、链接等一系列动作,得到一个可执行文件greet,可以尝试运行它:
# ./greet
hello!Bob!
hello!Andy!
hello!Harry!
一、静态库
有了以上的基础,就能知道,其实静态库已经在链接的时候被引入了。也就是一堆目标文件最终变成可执行文件。
那么一提到"库“,就想到比如stdio.h之类的库,因为我们要使用printf函数,这种函数就是”库函数“。现在需要注意的是,在链接的时候cpp文件已经变成.o文件了,所以最终我们从库中引入实际上就是另外加了一些目标文件。
现在尝试一下如何来制作一个库吧!假如说,我想制造一个”数学库“,里面包含斐波那契函数和阶乘函数。
//
// Created by wang66 on 2020/5/24.
// fib.cpp
unsigned long fib(unsigned long n){
return n<=1 ? 1 : fib(n-1)+fib(n-2);
}
//
// Created by wang66 on 2020/5/24.
// fac.cpp
unsigned long fac(unsigned long n){
return n<=1 ? 1 : n*fac(n-1);
}
接下来要获得.o文件:
g++ -std=c++11 -c fac.cpp fib.cpp
顺便一提,如果不加-o选项,默认地会将XXX.cpp生成XXX.o 。
利用ar命令生成静态库.a文件。要求命名以lib开头,以.a结尾。
ar -rsv libmath2.a fib.o fac.o
为了使用这俩函数,我们为他制作一个头文件:
//
// Created by wang66 on 2020/5/24.
// myselfmath2.h
#ifndef UNTITLED4_MYSELFMATH2_H
#define UNTITLED4_MYSELFMATH2_H
unsigned long fib(unsigned long n);
unsigned long fac(unsigned long n);
#endif //UNTITLED4_MYSELFMATH2_H
这样为了避嫌,删除 fac.o fac.cpp fib.o fib.cpp:
rm -rf fib.* fac.*
修改greet.cpp 如下所示:
//
// Created by wang66 on 2020/5/24.
// greet.cpp
#include <iostream>
#include "myselfmath2.h"
using namespace std;
int main(){
for(int i=2;i<=6;++i){
cout<<"fac("<<i<<")="<<fac(i)<<endl;
cout<<"fib("<<i<<")="<<fib(i)<<endl;
}
cout<<"ok,done."<<endl;
}
我们引用了两个头文件里的函数,但是我们已经把函数定义删除了,现在就需要利用 -L选项指定库的搜索路径,利用 -l(小写的L)来指定库的名字,如果名字叫<name>,实际上的库文件是lib<name>.a 。有了以上知识,我们利用g++来编译出一个新的greet。
先重新生成一个greet.o:
g++ -c greet.cpp -std=c++11
然后在链接的时候引入静态库:
g++ greet.o -L. -lmath2 -o new_greet
生成一个可执行文件new_greet,这个命令中-L指明静态库的搜索路径,-l指明<name>。
运行可得:
fib(2) = 2
fac(2) = 2
fib(3) = 3
fac(3) = 6
fib(4) = 5
fac(4) = 24
fib(5) = 8
fac(5) = 120
fib(6) = 13
fac(6) = 720
完全OK。
二、动态链接库
其实静态链接库与动态链接库最大的区别就是动态链接是在程序要运行时完成的,静态链接可能会很耗费内存,因为一个库程序可能要保存多份。虽然有小小的性能损失,但是却非常方便。
现在尝试着制作一个动态链接库,现在我们有两个文件:
//
// Created by wang66 on 2020/5/24.
// avg.cpp
double avg_of(double d1,double d2){
return (d1+d2)/2;
}
和:
//
// Created by wang66 on 2020/5/24.
// hello.cpp
#include <iostream>
void say_hello(const char* s){
std::cout<<"hello!"<<s<"!"<<std::endl;
}
利用-shared选项编译出一个so文件,-fPIC表示编译出一份地址无关代码:
g++ -std=c++11 -fPIC -shared -o myselflib.so avg.cpp hello.cpp
现在为这个写一个头文件:
g++ -o greet2 greet.o ./myselflib.so
改写greet.cpp:
//greet.cpp
#include <iostream>
#include "myfirstlib.h"
using namespace std;
int main(){
say_hello("wang66");
cout<<"avg of 2 and 10 is"<<avg_of(10,2)<<endl;
}
生成greet2可执行文件:
g++ -o greet2 greet.o ./myselflib.so
或者我们可以按照上述静态库的命名规则重新生成一下,这样就可以用-l 和-L选项了。
g++ -std=c++11 -fPIC -shared -o libmyself2.so avg.cpp hello.cpp
g++ -std=c++11 -L. -lmyself2 -o greet3 greet.cpp
值得一提的是,使用动态库完全不需要greet.o文件,只要传入greet.cpp,那么g++就帮我们做了一切工作。
# ./greet3
hello!wang66!
avg of 2 and 10 is6
所以一般我们在Makefile中见到的一长串命令,实际上都是动态链接,很少用静态库。