C++生成静态库和动态库
什么是静态库和动态库
在项目开发中,或多或少地需要使用到第三方(非编译器提供)的程序库,使用第三方的程序库能够减少重复造轮子的工作,提高开发效率。本文将介绍如何把自己的写的程序制作为程序库提供给他人使用,学会制作程序库后,自然也就会掌握了如何使用他人提供的程序库了。
程序库从使用方式上分为两种,静态库和动态库。当我们在使用第三方提供的静态库时,当编译程序时,需要将我们自己写的程序和第三方库链接在一起形成一个单独的程序。而当使用第三方提供的动态库时,虽然我们使用了动态库提供的功能,但是我们只需要单独编译我们自己写的程序,当程序运行时并使用到了动态库中提供的功能时,才会把动态库中对应的程序的二进制代码加载进内存中。
静态库和动态库主要在使用上有些差别,其本质经过源程序编译、汇编后成为的二进制代码。下面的内容将在 linux 下进行实验,介绍如何制作静态库和动态库。
制作静态库
先准备实验需要的代码,如下所示,不用去关心代码细节。
.
├── include
│ └── head.h
├── main.cc
└── src
├── add.cc
├── div.cc
├── mult.cc
└── sub.cc
2 directories, 6 files
// main.cc 为用于测试的代码
#include <iostream>
#include "./include/head.h"
int main()
{
std::cout << add(1, 2) << std::endl;
std::cout << sub(5, 2) << std::endl;
std::cout << mult(2, 3) << std::endl;
std::cout << divi(5, 2) << std::endl;
}
第一:将需要制作为库的源代码 *.cc
编译、汇编为二进制目标文件 ``*.o` 。
$ g++ src/*.cc -c
$ tree
.
├── add.o
├── div.o
├── include
│ └── head.h
├── main.cc
├── mult.o
├── src
│ ├── add.cc
│ ├── div.cc
│ ├── mult.cc
│ └── sub.cc
└── sub.o
第二:将生成的二进制文件打包成静态库
$ ar rcs libcalc.a *.o
# 生成名为 libcalc.a 的静态库
在linux下,静态库的命名方式为 libxxx.a,中间的 xxx 为程序库的名字。windows下,静态库的命名方式为 libxxx.lib。
这里简单介绍一下“打包”工具 ar
。
The GNU ar program creates, modifies, and extracts from archives. An archive is a single file holding a collection of other files in a structure that makes it possible to retrieve the original individual files (called members of the archive).
ar
程序可以把多个文件合为一个文件,然后访问合并后的文件和访问合并前的文件是一样的。
简单解释一下使用 ar 打包程序库的三个参数 r
, c
, s
r
, replace existing or insert new file(s) into the archivec
, do not warn if the library had to be createds
, create an archive index (cf. ranlib)
第三步:提供静态库和头文件。将生成的静态库和对应的头文件发布出去,就可以使用该静态库了。例如在上面的例子中,将 include/head.h
和 libcalc.a
提供出去。
使用静态库
仍然以上面的示例为例,将测试文件、静态库及其对应的头文件放在一个目录下,便于实验测试。
$ tree
.
├── head.h
├── libcalc.a
└── main.cc
$ g++ main.cc -L ./ -lcalc -o main
$ ./main
3
3
6
2.5
-L
, 指定程序库的搜索路径;-l
指定程序库的名称,需要掐头(lib)去尾(.a)。
制作动态库
仍然以上面的代码为示例。在 linux 中,动态库以 lib
为前缀,.so
为后缀,例如 libcalc.so
。在 windows 中,动态库以 lib
为前缀,以 dll
为后缀,例如 libcalc.dll
。
第一步:将需要制作为动态库的源文件编译、汇编为二进制文件。
$ g++ src/*.cc -c -fpic
# 生成 *.o 二进制文件
-fpic
选项的作用是,生成位置无关代码(Position Independent Code,PIC)或者说是生成共享库时的代码。
(以下来自 ChatGPT,仅供参考)
PIC是一种特殊的代码,可以在内存中的任何位置执行而不受影响。它是动态链接库(shared library)的一种关键概念。当你编译一个动态链接库时,通常希望库的代码可以在内存中的不同位置加载,而不是绑定到特定的地址。这种灵活性使得同一份共享库可以被多个进程共享,因为它们可以在内存的不同位置加载,而不会相互冲突。
第二步:使用 gcc/g++ 命令生成动态库
$ g++ -shared *.o -o libcalc.so
-shared
选项用于告诉编译器生成一个动态库。
第一步和第二部也可以合并为一步,如下命令所示
$ g++ src/*.cc -shared -o libcalc.so -fpic
第三步:将制作的动态库和对应的头文件发布出去。
使用动态库
尝试使用和静态库一样的方式进行编译。
$ g++ main.cc -L ./ -lcalc -o main
$ ./main
./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
可以看到,能成编译通过,但是运行报错,报错的内容为:找不到动态库。这是因为动态库不会和程序一起编译,上述编译编译的命令只是告诉了编译器在编译时能够找到动态库,但是当实际程序运行时,却搜索不到需要使用的动态库,因此程序报错。程序运行时,动态库的查找和内存加载操作是由 动态链接器 来完成的。
(以下来自ChatGPT)
动态链接器(Dynamic Linker)是操作系统的一部分,负责在程序运行时将程序中使用的动态库(共享库)加载到内存中,并解析程序与库之间的符号链接。
动态链接器搜索动态的路径为 /lib
或 /usr/lib
,因此解决办法之一就是将动态库拷贝到 /lib
或 usr/lib
目录下,然后就可以在运行时成功加载了。
$ cp /xxx/xxx/libcalc.so /usr/lib/
$ g++ main.cc -lcalc -o main
$ ./main
3
3
6
2.5
小结
本文介绍了静态库和动态库的区别,以及如何制作、使用静态库和动态库。但本文只是以简单的示例进行演示,对于复杂的项目,生成程序库需要需要借助 shell 脚本或 CMake 来完成一些复杂的逻辑操作。
参考资料
Linux 静态库和动态库