库是写好的、可复用的代码。现实中每个程序都要依赖很多底层库,不可能从零开始。本质上库是一种可执行代码的二进制形式,可被操作系统载入内存执行。库有两种:静态库(.a/.lib)和动态库(.so/.dll)——Windows 上是 .lib/.dll,Linux 上是 .a/.so。
先介绍 Linux 下 gcc 编译的几个选项:
g++ -c hellospeak.cpp
-c 表示编译源代码但不链接,输出为对象文件(默认与源码同名、后缀改为 .o),上面命令生成 hellospeak.o。下面这条命令把两个源码文件编译链接成单一可执行程序:
g++ hellospeak.cpp speak.cpp -o hellospeak
不加 -o 时编译器默认输出 a.out。所谓”静态/动态”,指的就是链接方式。
静态库
之所以叫”静态库”,是因为在链接阶段,汇编生成的目标文件 .o 与引用到的库一起打包进可执行文件,这种链接方式称为静态链接。静态库可以看作一组目标文件(.o/.obj)的集合,即很多目标文件压缩打包后的文件。特点:
- 静态库对函数库的链接在编译期完成;
- 程序运行时与函数库再无瓜葛,移植方便;
- 浪费空间和资源——所有相关目标文件与函数库被链接成一个可执行文件。
命名规则
Linux 静态库命名规范为 lib[your_library_name].a:lib 为前缀,中间是库名,扩展名为 .a。
创建静态库
- 将代码文件编译成目标文件 .o:
g++ -c StaticMath.cpp
(注意带 -c,否则直接编译为可执行文件)
- 用
ar工具将目标文件打包成 .a 静态库:
ar -crv libstaticmath.a StaticMath.o
生成静态库 libstaticmath.a。
动态库
静态库易用、能复用代码,为什么还需要动态库?
- 空间浪费是静态库的一个问题。
另一个问题是静态库给程序的更新、部署和发布带来麻烦:如果静态库 liba 更新了,所有使用它的应用程序都要重新编译发布(对用户而言,很小的改动就要全量重新下载)。
动态库在程序编译时并不连接到目标代码,而是在运行时才载入。不同应用程序调用相同的库,内存里只需要一份共享库实例,规避了空间浪费;运行时才载入,也解决了更新部署的麻烦——用户只需增量更新动态库。特点:
- 动态库把对库函数的链接推迟到程序运行期;
- 可实现进程间资源共享(因此动态库也称共享库);
- 程序升级更简单;
- 甚至可做到链接载入完全由程序员在代码中控制(显式调用)。
Windows 与 Linux 的差异
- Windows 执行文件格式是
PE,动态库需要一个 DllMain 函数作初始化入口,导出函数声明通常需要_declspec(dllexport); - Linux 下 gcc 默认编译 ELF 格式,不需要初始化入口,也不需要特别声明函数,编写更方便。
与创建静态库不同,创建动态库不需要打包工具(ar、lib.exe),直接用编译器即可。