静态库和动态库是两种常见的库文件,它们在软件开发中扮演着不同的角色。它们的主要区别在于它们被链接到程序中的方式以及程序运行时的行为。
1 库
1.1 静态库(Static Library):
- 静态库是一组预编译的目标文件的集合,其中包含了函数和数据,这些函数和数据可以被程序调用和使用。
- 在编译链接过程中,静态库的内容会被完整地复制到生成的可执行文件中。
- 静态库一旦被链接到程序中,在程序运行时就不会发生变化,因为静态库的代码被完全复制到了可执行文件中。
- 使用静态库的优点是在程序执行时不需要外部依赖,但缺点是会增加可执行文件的大小,而且如果多个程序使用同一个静态库,会造成存储资源的浪费。
1.2 动态库(Dynamic Library):
- 动态库也是一组预编译的目标文件的集合,其中包含了函数和数据,同样可以被程序调用和使用。
- 在编译链接过程中,动态库的引用会被放置到生成的可执行文件中,但动态库的实际代码并不会复制到可执行文件中。
- 在程序运行时,可执行文件会在需要的时候加载动态库,并将其映射到内存中,程序可以在运行时动态地调用动态库中的函数。
- 使用动态库的优点是减少了可执行文件的大小,因为动态库的代码是在运行时加载的,多个程序可以共享同一个动态库,节省了存储资源。
- 动态库的缺点是在运行时需要外部依赖,如果动态库缺失或版本不匹配,程序可能无法正常运行。
2.1 创建静态库
1、编译源代码生成目标文件(.o)在 obj
目录下生成 file1.o
和 file2.o
目标文件。
gcc -c file1.c file2.c -o obj/file1.o obj/file2.o
2、打包为静态库文件(.a)
ar crv libmylib.a obj/file1.o obj/file2.o
在当前目录下生成 libmylib.a
静态库文件,其中 obj/file1.o
和 obj/file2.o
是目标文件。
参数解析:
如果需要将一个目录下的所有 .o
文件打包为一个静态库文件,可以使用通配符 *
,如下:
ar crv libmylib.a obj/*.o
c
:创建静态库文件
r
:插入或更新静态库文件中的文件
v
:标准输出每个文件的名称
2.2 使用静态库文件
在需要使用静态库文件的代码文件中,需要包含头文件(如果有),并在编译时指定静态库文件的路径和名称,如下:
gcc main.c -o main -L. -lmylib
解释一下这个命令:
-L.
:添加当前目录到链接器搜索目录列表,这样链接器就能找到静态库文件。
-lmylib
:指定要链接的库文件,链接器会按照约定的命名方式(libxxx.a
)搜索链接的静态库文件,所以只需要在命令中指定库文件的名称,不需要包括.a
扩展名。
如果需要将静态库文件安装到系统目录下供其他程序使用,可以将库文件复制到 /usr/lib
或 /usr/local/lib
目录下,并使用 ldconfig
命令更新动态链接库缓存:
sudo cp libmylib.a /usr/lib
sudo ldconfig
这样其他程序就可以直接使用 libmylib.a
库文件了。
2.3 静态库的示例
当然,让我们通过一个简单的示例来详细说明如何在Linux环境下创建一个C语言的静态库,并在另一个程序中使用这个静态库。我们将创建一个静态库,该库包含两个函数:一个用于计算两数之和,另一个用于计算两数之差。
步骤1:编写源代码
首先,我们创建两个C源文件,分别实现加法和减法功能。
// add.c
int add(int a, int b) {
return a + b;
}
// subtract.c
int subtract(int a, int b) {
return a - b;
}
步骤2:编译源文件为对象文件
接下来,我们需要将这两个源文件编译成对象文件(.o文件)。在终端中执行以下命令:
gcc -c add.c
gcc -c subtract.c
这将生成 add.o 和 subtract.o 两个对象文件。
步骤3:创建静态库
然后,我们使用ar命令将这两个对象文件打包成一个静态库文件。假设我们想命名为 libmathlib.a:
ar rcs libmathlib.a add.o subtract.o
步骤4:编写使用静态库的程序
现在,我们创建一个主程序 main.c,它将使用我们刚创建的静态库中的函数:
// main.c
#include <stdio.h>// 前向声明,告诉编译器这些函数存在于某个地方
int add(int, int);
int subtract(int, int);int main() {
int x = 5;
int y = 3;
printf("Sum: %d\n", add(x, y));
printf("Difference: %d\n", subtract(x, y));
return 0;
}
步骤5:编译并链接静态库
最后,我们编译 main.c 并链接到我们的静态库。这里需要注意的是,因为我们没有安装静态库到系统标准路径,所以需要指定库的路径以及库名(去掉前缀 lib 和后缀 .a):
gcc main.c -L. -lmathlib -o my_program
这里的 -L. 表示在当前目录下寻找库,-lmathlib 指定了要链接的库名为 mathlib。
步骤6:运行程序
编译完成后,直接运行生成的可执行文件即可:
./my_program
输出应该是:
Sum: 8
Difference: 2
这样,我们就成功地创建了一个静态库,并在一个简单的程序中使用了它。
2.4 创建动态库
动态库(也称为共享库)与静态库不同,它在运行时被动态加载到内存中,因此可以被多个程序共享,从而减少内存的使用,同时也方便了更新和维护。下面是创建动态库的基本步骤:
1 编译源代码生成目标文件(.o)
gcc -fPIC -c file1.c file2.c -o obj/file1.o obj/file2.o
-fPIC
参数告诉编译器生成位置独立的代码,这是创建动态库所必需的。
2 打包为动态库文件(.so)
gcc -shared -o libmylib.so obj/file1.o obj/file2.o
这个命令将目标文件链接成一个名为libmylib.so的动态库文件。
2.5 使用动态库文件
在需要使用动态库文件的代码文件中,需要包含头文件(如果有),并在编译时指定动态库文件的路径和名称,如下:
gcc main.c -o main -L. -lmylib
参数
-L.
和-lmylib
的含义与静态库的使用方式相同,都是指定库文件的路径和名称。
如果需要将动态库文件安装到系统目录下供其他程序使用,可以将库文件复制到 /usr/lib
或 /usr/local/lib
目录下,并更新动态链接库缓存:
sudo cp libmylib.so /usr/lib
sudo ldconfig
这样其他程序就可以直接使用 libmylib.so
库文件了。
2.6 动态库的示例
动态库的创建和使用过程与静态库类似,但涉及到一些不同的编译和链接步骤,以确保库能够在运行时被加载。下面是在Linux环境下创建一个简单的C语言动态库,并在另一个程序中使用它的步骤。
步骤1:编写源代码
首先,我们同样创建两个C源文件,分别实现加法和减法功能。
// add.c
int add(int a, int b) {
return a + b;
}
// subtract.c
int subtract(int a, int b) {
return a - b;
}
步骤2:编译源文件为共享对象文件
与静态库不同,这里我们需要直接编译成共享对象(.so)文件。使用-fPIC选项生成位置无关代码,并使用-shared选项创建动态库。
gcc -fPIC -c add.c
gcc -fPIC -c subtract.c
gcc -shared -o libmathlib.so add.o subtract.o
这将生成名为 libmathlib.so 的动态链接库。
步骤3:编写使用动态库的程序
接下来,我们修改之前的 main.c 文件,以便使用动态库。注意,动态库的使用在代码层面与静态库相同,区别在于编译和链接阶段。
// main.c
#include <stdio.h>// 前向声明
int add(int, int);
int subtract(int, int);int main() {
int x = 5;
int y = 3;
printf("Sum: %d\n", add(x, y));
printf("Difference: %d\n", subtract(x, y));
return 0;
}
步骤4:编译并链接动态库
编译时,我们需要使用 -L 来指定动态库所在的目录,以及 -l 来指定库名(不包括 lib 前缀和 .so 后缀)。但是,直接这样编译可能找不到库,因为动态加载发生在运行时,所以我们通常只编译不链接:
gcc -o my_program main.c
为了运行时能够找到动态库,有几种方法:
将动态库复制到系统库目录,如 /usr/lib 或 /usr/local/lib。
设置环境变量 LD_LIBRARY_PATH 包含库文件的路径。
这里我们使用第二种方法,假设库就在当前目录下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
步骤5:运行程序
设置好环境变量后,就可以运行程序了:
./my_program
输出与之前相同:
Sum: 8
Difference: 2
这样,我们就完成了动态库的创建和使用过程。动态库的优势在于,多个程序可以共享同一份库的代码和数据,节省内存;且更新库时不需要重新编译链接依赖它的应用程序。