静态库和动态库
程序编译的四个阶段
- 预处理
- 编译
- 汇编
- 链接
每个阶段所做的工作
1, 预处理
宏替换 , 文件包含 (头文件展开等) , 条件编译 , 去注释
预处理指令是以
#
开头的代码行
gcc -E test.c -o test.i
选项
-E
是让编译器在预处理过后停止编译选项
-o
是指目标文件 ,.i
文件是预处理过后的原始 C 程序
2, 编译 (生成汇编代码)
对预处理过后的程序进行一系列的
词法分析、语法分析、语义分析
以及优化后产生相应的汇编代码文件
编译过程分为 6 步 :
- 扫描(词法分析)
- 语法分析
- 语义分析
- 源代码优化
- 代码生成
- 目标代码优化
gcc -S test.i -o test.s
选项
-S
使编译器在编译过后停止 , 不进行汇编 , 生成.s
汇编代码文件
3, 汇编 (生成机器可识别代码)
把编译阶段生成的
.s
文件转成二进制目标代码.o
文件
gcc -c test.s -o test.o
4, 链接 (生成可执行文件)
成功编译之后 , 就进入了链接阶段
gcc test.o -o test
函数库
我们在写代码的时候 , 用到了很多的库函数 , 但是我们并没有实现这些函数 , 我们包含的头文件也只有库函数的声明 , 没有实现 , 那么函数的实现在哪呢 ?
这些库函数都被封装到了一个名为
libc.so.6
的标准库文件之中链接的时候就是在库文件中去寻找库函数的实现 , 这样程序就能正常运行了
库文件其实就是可执行代码的二进制形式 , 可以被操作系统载入内存执行
库一般有两种
静态库
动态库 (共享库)
静态库 是指在编译链接时把库文件的代码全部加到可执行文件中 , 因此生成的文件比较大 ,
但是在运行时也就不需要库文件了 .
静态库的名称后缀在 Linux 下一般为 .a
, 在 Windows 下为 .lib
动态库 与之相反 , 在程序执行期间才去加载库文件 , 可以节省系统开销 .
动态库的名称后缀在 Linux 下一般为 .so
, 在 Windows 下为 .dll
Linux 下生成静态库和动态库
我们写一个 Math 类来测试动静态库的生成
math.h
class Math {
public:
double add(double& t_x, double& t_y);
double sub(double& t_x, double& t_y);
double mul(double& t_x, double& t_y);
double div(double& t_x, double& t_y);
};
math.cpp
#include "math.h"
double Math::add(double& t_x, double& t_y)
{
return t_x + t_y;
}
double Math::sub(double& t_x, double& t_y)
{
return t_x - t_y;
}
double Math::mul(double& t_x, double& t_y)
{
return t_x * t_y;
}
double Math::div(double& t_x, double& t_y)
{
return t_x / t_y;
}
测试代码
test_math.cpp
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
Math math;
double x, y;
cin >> x >> y;
cout << "x + y = " << math.add(x, y) << endl;
cout << "x - y = " << math.sub(x, y) << endl;
cout << "x * y = " << math.mul(x, y) << endl;
cout << "x / y = " << math.div(x, y) << endl;
}
现在我们如果直接 g++ test_math.cpp
肯定会报错 , 说我们的函数没有定义
下面我们生成一个静态库
g++ -c math.cpp
生成math.o
文件
ar -rc libmy_math.a math.o
生成libmy_math.a
静态库文件
- 使用静态库
因为编译器默认动态链接 , 如果想要静态链接可以使用
- static
选项
g++ -static test_math.cpp -o s_main -L. -lmy_math
其中 -o
选项是指定生成的可执行文件的名称
-L.
表示指定库的路径为本目录
-lmy_math
表示指定库名称 my_math
一般库的搜索路径为
- 从左到右搜索
-L
指定的目录- 由环境变量指定的目录
LIBRARY_PATH
- 由系统指定的目录
/usr/lib
/usr/local/lib
我们发现静态链接生成的可执行文件非常大 , 因为它把库文件的代码全都加入了可执行文件中
可以用 file
命令查看可执行文件的属性 , 发现是 statically linked
的
此时即便我们删除 libmy_math.a
库文件 , 程序仍然可以运行
接下来生成一个动态库
g++ -fPIC -c math.cpp
生成math.o
文件其中
-fPIC
选项表示 产生与位置无关码g++ -shared -o libmy_math.so math.o
生成libmy_math.so
动态库文件其中
-shared
选项表示生成共享库格式使用动态库
g++ test_math.cpp -o d_main -L. -lmy_math
因为编译器默认是动态链接的 , 所以不用加别的选项 , 直接指定路径和库名称就可以
此时生成了可执行文件 d_main
, 发现他比 s_main
小了很多
通过 file
命令查看
发现他是 dynamically linked
的 , 运行 d_main
也没问题 . 此时如果我们删除动态库文件 libmy_math.so
, 发现程序就无法运行了
编写 Makefile
自动化执行
.PHONY: build test clean
build: libmy_math.so
libmy_math.so: math.o
g++ -shared -o $@ $^
math.o: math.cpp
g++ -c -fPIC $^
test: d_main
d_main: test_math.cpp libmy_math.so
g++ $^ -o $@ -L. -lmy_math
LD_LIBRARY_PATH=. ./d_main
clean:
rm -f *.o *.so d_main
执行命令 make build
即可生成动态库文件
执行命令 make test
即可动态链接库文件生成可执行文件并运行
执行命令 make clean
即可清理生成的文件
总结
下面总结一下静态库和动态库的优缺点
静态库 | 动态库 |
---|---|
链接时将库文件加入可执行程序之中 | 程序运行时才去加载库文件 |
生成的可执行文件很大 | 生成的可执行文件较小 |
会产生多份冗余的副本 | 多进程共享 , 节省空间 |
运行时不依赖库文件 | 运行依赖库文件 |
全量更新 | 增量更新 |