一.静态库的制作
静态库是一组对象文件的集合,它们在编译时被链接到可执行文件中。这意味着,静态库中的代码会被复制到每个使用它的程序中,因此静态库不需要在程序运行时被单独加载。制作静态库可以帮助你将常用的代码模块化、重用,简化开发过程。
以下是创建静态库的详细步骤:
1.编写源代码
首先,创建几个C/C++源文件,它们将组成静态库。例如,创建两个c文件math_functions.c和string_functions.c,并为其编写相应的功能。
math_functions.c(数学函数)
#include <math_functions.h>int add(int a, int b) {return a + b;
}int subtract(int a, int b){return a - b;
}
math_functions.h
(数学函数头文件)
#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_Hint add(int a, int b);
int subtract(int a, int b);#endif
string_functions.c
(字符串处理函数)
#include "string_functions.h"
#include <string.h>int string_length(const char* str) {return strlen(str);
}int string_compare(const char* str1, const char* str2) {return strcmp(str1, str2);
}
string_functions.h
(字符串处理函数头文件)
#ifndef STRING_FUNCTIONS_H
#define STRING_FUNCTIONS_Hint string_length(const char* str);int string_compare(const char* str1, const char* str2);#endif
#ifndef MATH_FUNCTIONS_H 和 #define MATH_FUNCTIONS_H是防止重复包含头文件的预处理指令,称为头文件保护。如果 math_functions.h
已经被包含过,这一对指令可以防止重复定义导致的编译错误。
2.编译源文件
将每个.c源文件编译为.o对象文件。这些对象将被包含在静态库中。你可以使用gcc或g++编译器完成此步骤。
gcc -c math_functions.c -o math_functions.o
gcc -c string_functions.c -o string_functions.o
解释:
-c
表示编译源文件但不链接,生成.o
对象文件。-o
指定输出文件的名称。
现在你应该有两个对象文件:math_functions.o
和 string_functions.o
。
3.创建静态库
接下来,使用ar(archive utility)工具将这些.o文件打包成一个静态库。静态库的扩展名通常是.a。
ar rcs libmylibrary.a math_functions.o string_functions.o
解释:
ar
是创建静态库的工具。r
表示插入文件到库(如果库不存在则创建)。c
表示创建库。s
表示索引库,创建符号表,使得在链接时可以快速找到库中的符号。libmylibrary.a
是创建的静态库的名称,通常库名以lib
开头。- 后面的
.o
文件是要打包进库中的对象文件。
运行完这个命令后,当前目录下会生成一个名为 libmylibrary.a
的静态库。
4.使用静态库
接下来,演示如何使用这个静态库。首先,编写一个C程序来调用库中的函数。
main.c(测试程序)
#include "math_functions.h"
#include "string_functions.h"
#include <stdio.h>int main() {int a = 10, b = 5;printf("Add: %d\n", add(a, b));printf("Subtract: %d\n", subtract(a, b));const char* str1 = "Hello";const char* str2 = "World";printf("Length of str1: %d\n", string_length(str1));printf("Comparison of str1 and str2: %d\n", string_compare(str1, str2));return 0;
}
5.链接静态库
要使用你创建的静态库,编译和链接你的测试程序时,必须指定库的路径和名称。
gcc main.c -L. -lmylibrary -o myprogram
解释:
-L.
指定库的搜索路径为当前目录(.
表示当前目录)。-lmylibrary
告诉链接器使用libmylibrary.a
静态库(省略lib
和.a
后缀)。-o myprogram
指定输出的可执行文件名为myprogram
。
编译成功后,myprogram
会是可执行文件,运行它将会输出结果:
./myprogram
输出可能如下:
Add: 15
Subtract: 5
Length of str1: 5
Comparison of str1 and str2: -1
6. 查看静态库内容
你可以使用 ar
工具查看静态库的内容,看看其中包含了哪些对象文件:
ar t libmylibrary.a
输出可能为:
math_functions.o
string_functions.o
静态库的特点
- 静态链接:在编译时,静态库中的代码会被复制到目标程序中,程序不依赖外部库运行。
- 独立性:使用静态库的可执行程序在运行时不需要静态库文件的存在,因为代码已经被嵌入到可执行文件中。
- 性能:由于代码在编译时被直接链接到程序,运行时不需要动态加载库,因此静态库可能比动态库运行时略快。
- 缺点:可执行文件会变得更大,因为每个使用库的程序都包含了库的副本。而且,如果库有更新,所有依赖该库的程序都需要重新编译。
二.动态库的制作
制作动态库(也称为共享库)在不同操作系统上略有差异,但基本步骤相似。以下是针对 Unix/Linux 系统中使用 gcc
制作动态库的详细步骤:
1.编写源文件
首先,需要创建一个或多个源文件,这些文件包含要编译到动态库中的函数。例如,假设我们要创建一个简单的数学库,源文件内容如下:
calc.c
#include <stdio.h>int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}
calc.h
#ifndef CALC_H
#define CALC_Hint add(int a, int b);
int subtract(int a, int b);#endif
2. 编译为目标文件(.o
文件)
接下来,将源文件编译为目标文件(.o
文件),并使用 -fPIC
选项生成与位置无关的代码(Position-Independent Code),以便它在内存中可以被动态库安全加载到任意地址:
gcc -c -fPIC calc.c -o calc.o
-c
:只编译,不链接。-fPIC
:生成与位置无关的代码,这对于动态库是必须的。-o calc.o
:指定输出目标文件的名称为calc.o
。
3. 创建动态库
使用生成的目标文件创建动态库。动态库在 Unix/Linux 系统中以 .so
(共享对象,shared object)为扩展名。使用 -shared
选项来生成动态库:
gcc -shared -olibcalc.so calc.o
-shared
:指示编译器生成一个动态库。-o libcalc.so
:指定输出文件名为libcalc.so
。按照惯例,动态库的名称通常以lib
开头,例如libcalc.so
。这使得在链接时只需要指定库的名称,如-lcalc
,而编译器会自动寻找libcalc.so
。
4. 使用动态库
为了使用生成的动态库,需要在编译可执行文件时指定库的搜索路径和库名称。假设使用上述动态库编译一个主程序 main.c
:
main.c
#include <stdio.h>
#include "calc.h"int main() {int x = 5, y = 3;printf("Add: %d\n", add(x, y));printf("Subtract: %d\n", subtract(x, y));return 0;
}
编译时,需要使用 -L
和 -l
选项指定库的路径和名称:
gcc main.c -o -L. -lcalc
-L.
:指定当前目录(.
)为库的搜索路径。-lcalc
:链接名为libcalc.so
的动态库。
5. 运行程序
在运行编译后的可执行文件时,系统需要知道动态库的位置。可以使用环境变量 LD_LIBRARY_PATH
来指定动态库的路径。例如:
export LD_LIBRARY_PATH=.
./app
export LD_LIBRARY_PATH=.
:将当前目录添加到动态库搜索路径。./app
:运行可执行文件。
6. 安装动态库(可选)
为了使动态库在任何位置都可用,可以将其复制到系统的标准库目录(如 /usr/lib
或 /usr/local/lib
):
sudo cp libcalc.so /usr/local/lib/
然后,更新库缓存:
sudo ldconfig
这样就不需要每次运行程序时都设置 LD_LIBRARY_PATH
,编译时也可以直接链接库:
gcc main.c -o app -lcalc
总结
制作动态库的步骤包括:
- 编写源文件并编译为位置无关的目标文件(
.o
文件)。 - 使用
-shared
选项将目标文件打包为动态库(.so
文件)。 - 编译使用动态库的程序时,指定库路径和库名称。
- 运行程序时确保动态库在可搜索的路径中(使用
LD_LIBRARY_PATH
或安装到系统库目录)。
这种方式可大大减小可执行文件的大小,并方便代码的复用和更新。
三.静态库和动态库的对比
静态库和动态库是两种用于代码共享和重用的库文件形式,它们各自有不同的特性和适用场景。以下是对两者的详细对比:
静态库和动态库是两种用于代码共享和重用的库文件形式,它们各自有不同的特性和适用场景。以下是对两者的详细对比:
1. 定义
-
静态库(Static Library):
- 静态库是代码的集合,在编译可执行文件时,将所需的库代码复制到目标文件中。
- 通常以
.a
(在 Unix/Linux 上)或.lib
(在 Windows 上)为扩展名,例如libcalc.a
。
-
动态库(Dynamic Library/Shared Library):
- 动态库在程序运行时被加载,不会在编译时将库代码嵌入到可执行文件中。
- 在 Unix/Linux 系统中,动态库以
.so
为扩展名(共享对象,shared object),例如libcalc.so
;在 Windows 系统中,以.dll
为扩展名(动态链接库,dynamic link library)。
2. 编译和链接
静态库:
- 在编译可执行文件时,库的代码会直接被复制到目标文件中,这意味着生成的可执行文件已经包含了所需的所有库代码。
- 可执行文件较大,因为包含了库的代码。
- 使用静态库时,编译命令通常类似于:
gcc main.c -o app -L. -lcalc
动态库:
- 在编译时,动态库不会被复制到目标文件中,只是对库的引用,程序在运行时才加载库的代码。
- 可执行文件较小,因为库的代码不被嵌入其中。
- 动态库的使用通常也使用类似的命令:
gcc main.c -o app -L. -lcalc
3. 运行时行为
-
静态库:
- 程序一旦编译完成,就不再依赖静态库文件。即使之后静态库被删除,程序仍然可以正常运行。
- 因为静态库的代码被嵌入到可执行文件中,不同的可执行文件即使使用同一个静态库,库的代码也会被复制多份。
-
动态库:
- 程序在运行时需要动态库文件。如果动态库文件缺失或路径不正确,程序将无法启动。
- 多个程序可以共享同一个动态库的代码段,节省内存资源。
4. 文件大小
-
静态库:
- 可执行文件较大,因为它包含了所有静态链接的库代码。
- 库文件本身的大小取决于其中包含的函数和数据的多少。
-
动态库:
- 可执行文件较小,因为它不包含库代码,只包含对动态库的引用。
- 库文件本身可能较大,但在内存中可以由多个程序共享,节省总内存占用。
5. 性能
-
静态库:
- 程序启动时较快,因为所有代码已嵌入到可执行文件中,无需在运行时加载额外的库。
- 由于代码在编译时确定,程序运行时不需要进行库链接,性能更稳定。
-
动态库:
- 程序启动时稍慢,因为需要在运行时加载库文件,并进行符号解析。
- 由于动态链接,库的升级和错误修复更为灵活,不需要重新编译可执行文件。
6. 更新和维护
-
静态库:
- 更新库代码后,需要重新编译所有使用该库的可执行文件,以引入新的库代码。
- 使用静态库的程序不受库的后续修改影响。
-
动态库:
- 更新库文件后,所有依赖它的程序都会自动使用新的库版本,无需重新编译可执行文件。
- 可以进行库的热更新和修复,但也可能导致不兼容性问题(如果新的库版本与旧的接口不兼容)。
7. 内存占用
-
静态库:
- 每个可执行文件都有独立的库代码拷贝,因此多个程序运行时,整体内存占用较大。
-
动态库:
- 共享库在内存中只加载一次,多个程序可以共享同一个动态库的代码段,节省内存。
8. 平台兼容性
-
静态库:
- 静态库的代码在编译时被嵌入到可执行文件中,不受系统动态链接器的限制,具有更好的跨平台能力。
-
动态库:
- 动态库在不同操作系统和不同版本的操作系统之间可能存在兼容性问题,需要确保动态库的 ABI(应用程序二进制接口)兼容性。
9. 安全性
-
静态库:
- 程序代码一经编译就不再依赖外部库,因此比较安全,不会受到库文件的恶意篡改。
-
动态库:
- 程序在运行时需要加载动态库,如果动态库被篡改或替换(例如使用不受信任的库版本),可能会导致程序安全风险。
总结
特性 | 静态库 | 动态库 |
---|---|---|
文件扩展名 | .a (Unix/Linux),.lib (Windows) | .so (Unix/Linux),.dll (Windows) |
文件大小 | 大 | 小 |
运行时行为 | 不依赖库文件 | 依赖库文件 |
启动性能 | 较快 | 较慢 |
内存占用 | 较大(不共享) | 较小(共享) |
更新和维护 | 需要重新编译 | 更新库文件即可 |
安全性 | 较高 | 较低(需注意库文件的安全) |
使用建议
- 静态库:适用于不频繁更新且需要独立运行的程序,或者追求更高的运行稳定性。
- 动态库:适用于需要节省内存、方便升级、共享库代码的场景,例如系统库、常用的第三方库等。