在软件开发中,库 是一组已编译的代码集合,提供了程序可以直接调用的功能模块(如数学运算、字符串处理、文件操作等)。库的主要作用是提高代码复用性、减少重复开发,并提供标准化功能。
什么是库
库(Library) 是一个包含函数、类或其他可重用代码的集合。开发者在程序中调用库中的函数或功能,避免从零开始编写程序。
根据特定的功能开发的代码模块,供其它应用程序调用。一般来说库本身不提供应用代码。有的是源代码,比如 python 库,网页前端库 vue、jQuery。有的是经过编译的目标代码,比如 C 程序,Java 的 jar 包。C 语言库分为动态库和静态库,在 Windows 系统中动态库以 .dll
为后缀,静态库以 .lib
为后缀;Linux 系统动态库以 .so
为后缀,静态库以 .a
为后缀。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于windows和linux的本质不同,因此二者库的二进制是不兼容的。
linux下的库有两种:
静态库和共享库(动态库)。二者的不同点在于代码被载入的时刻不同。
C 语言库分为动态库和静态库,在 Windows 系统中动态库以 .dll 为后缀,静态库以 .lib 为后缀;Linux 系统动态库以 .so 为后缀,静态库以 .a 为后缀。
静态库
在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
动态库
在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行
时还需要动态库存在,因此代码体积较小。
库的形式:
- 源代码库:以源代码形式提供(如
.c
或.h
文件)。 - 二进制库:以已编译的二进制形式提供(如
.a
、.so
、.dll
等)。
根据链接方式和使用时机,库分为:
- 静态库(Static Library):在编译时链接到程序中。
- 动态库(Dynamic Library):运行时动态加载到程序中。
库的意义:
库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
静态库
1> 静态库的特点
静态库可用于静态编译,编译时需要将静态库合并到可执行程序中,因此会增大可执行程序的大小,但在执行时不需要加载库,直接跳转执行即可。
静态库对函数库的链接是放在编译时期(compile time)完成的,它在程序编译的时候被直接打包进可执行文件。
编译后,静态库与目标文件结合,形成一个独立的可执行程序。
静态库的扩展名:
* Linux/Unix:.a(archive)
* Windows:.lib
优点:
1. 程序在运行时与函数库再无瓜葛,不依赖外部库,移植方便。
2. 加载速度快:所有依赖已经嵌入可执行文件,运行时不需要动态加载。
缺点:
1. 文件体积大:每个程序都包含静态库的副本,占用更多存储空间。---> 浪费空间和资源,因为所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)。
2. 更新起来不方便:如果库更新,需要重新编译所有依赖该库的程序。
2> 制作静态库
2.1> 创建静态库的源文件(编写一个简单的 C 函数):
/*n个自然数之和的计算*/
int mysum(int n)
{
int sum = 0;
/*判断传入的数字是否为自然数*/
if(n <= 0)
return -1;
else
{
/*做n个自然数之和的计算保存到sum中*/
for(int i = 1; i <= n;i++)
{
sum += i;
}
}
return sum;
}
2.2> 创建静态库的头文件:
#ifndef __MYSUM_H__ //防止头文件被重复包含
#define __MYSUM_H__
int mysum(int n);
#endif
2.3> 编译生成.o文件:
gcc -c mysum.c -o mysum.o
2.4> 将.o文件制作成静态库 (命名为lib库名.a) / 使用 ar 工具创建静态库:
ar crs libmysum.a mysum.o
# ar的 c参数表示为创建一个档案文件
# r参数表示为将文件添加到所创建的库中
# s参数表示为生成索引以提高库被链接时的效率
3> 测试静态库
3.1> 编写一个测试代码:
#include <stdio.h>
#include "mysum.h"
int main(void)
{
int n;
printf("请输入一个正整数:\n");
scanf("%d",&n);
printf("自然数%d 的和结果为%d\n",n,mysum(n));
return 0;
}
3.2> 编译并连接静态库:
gcc test.c -o test -lmysum -L. //-l链接了mysum这个库,-L声明了该库的路径在当前目录
-l库名 --- 表示链接库(诶路)
-L库路径 --- 表示指定链接库的所在路径
3.3> 运行测试代码:
farsight@ubuntu:~/shared/static_lib$ ./test
请输入一个正整数:
10
自然数10 的和结果为55
farsight@ubuntu:~/shared/static_lib$ ./test
请输入一个正整数:
100
自然数100 的和结果为5050
以上。便是静态库的制作 与 在开发中的使用。
动态库
1> 动态库的特点
动态库可用于动态编译,编译时并不会将动态库合并到可执行代码中,而是在可执行代码中创建对动态库的链接。因此使用动态编译的可执行文件就小。动态编译的可执行程序运行时,需要动态加载库到进程中进行调用。
动态库把对一些库函数的链接载入推迟到程序运行的时期(runtime)。 动态库在程序运行时由操作系统动态加载。
可以实现进程之间的资源共享。
将一些程序升级变得简单。
甚至可以真正做到链接载入完全由程序员在程序代码中控制。
动态链接库的名字形式为 “libxxx.so” 后缀名为 “.so”:
动态库的扩展名:
* Linux/Unix:.so(shared object)
* Windows:.dll(dynamic link library)
针对于实际库文件,每个共享库都有个特殊的名字“soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
在文件系统中,soname仅是一个链接到实际动态库的链接。
对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件。这个时候soname是没有版本号的。
动态库的优点:1.节省存储空间:多个程序可以共享同一个动态库。2.便于更新:更新动态库后,所有依赖的程序自动使用新版本,无需重新编译。
动态库的缺点:1.依赖性:程序运行时需要动态库,缺少库会导致程序无法启动。2.加载时间:程序运行时需要加载动态库,启动速度较静态库慢。
2> 制作动态库
2.1> 创建动态库的源文件:
/*n个自然数之和的计算*/
int mysum(int n)
{
int sum = 0;
/*判断传入的数字是否为自然数*/
if(n <= 0)
return -1;
else
{
/*做n个自然数之和的计算保存到sum中*/
for(int i = 1; i <= n;i++)
{
sum += i;
}
}
return sum;
}
2.2> 创建动态库的头文件:
#ifndef __MYSUM_H__ //防止头文件被重复包含
#define __MYSUM_H__
int mysum(int n);
#endif
2.3> 编译生成.o文件:
gcc -fPIC -Wall -c mysum.c
# -fPIC:生成位置无关代码(Position-Independent Code)。
2.4> 将.o文件制作动态库(命名为lib库名.so):
gcc -shared -o libmysum.so mysum.o //shared指定生成动态链接库
3> 测试动态库
3.1> 编写一个测试代码:
#include <stdio.h>
#include "mysum.h"
int main(void)
{
int n;
printf("请输入一个正整数:\n");
scanf("%d",&n);
printf("自然数%d 的和结果为%d\n",n,mysum(n));
return 0;
}
3.2> 编译并连接动态库:
gcc test.c -o test -lmysum -L. //-l链接了mysum这个库,-L声明了该库的路径在当前目录
-l库名 --- 表示链接库(诶路)
-L库路径 --- 表示指定链接库的所在路径
3.3> 装载动态库:
farsight@ubuntu:~/shared/dynamic_lib$ ./test
./test: error while loading shared libraries: libmysum.so: cannot open shared object file: No such file or directory
方法一:
将动态库拷贝到系统的库的目录中: /lib/ 或者/usr/lib
sudo cp libmysum.so /lib
方法二:
将库的路径添加到库的配置文件中:
打开配置文件: sudo vim /etc/ld.so.conf.d/my.conf
添加路径: /home/farsight/shared/dynamic_lib (根据自己实际的库的路径来添加)
是配置文件生效: sudo ldconfig
方法三:
将库的路径添加到库的环境变量中(只作用于当前终端,关闭后失效)
查看环境变量:
echo $LD_LIBRARY_PATH
添加库路径到环境变量中:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/farsight/shared/dynamic_lib
3.4> 运行测试代码:
farsight@ubuntu:~/shared/IO/day3/dynamic_lib$ ./test
请输入一个正整数:
10
自然数10 的和结果为55
farsight@ubuntu:~/shared/IO/day3/dynamic_lib$ ./test
请输入一个正整数:
100
自然数100 的和结果为5050
加载库
静态库的加载
静态库在编译阶段直接加载到可执行文件中,在编译时通过链接器(ld)完成库的合并。
动态库的加载
动态库的加载可以分为两种方式:
- 自动加载:
- 在程序启动时,操作系统根据程序的依赖自动加载动态库。
- 依赖库路径可以通过 LD_LIBRARY_PATH 环境变量指定(Linux)。
- 手动加载(动态加载):
- 程序运行时显式加载动态库,使用库中的函数。
- 在 Linux 中可以使用
dlopen()
,dlsym()
,dlclose()
。
动态加载:
#include <stdio.h>
#include <dlfcn.h>
int main() {
// 加载动态库
void *handle = dlopen("./libmath.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "Error: %s\n", dlerror());
return 1;
}
// 获取函数指针
int (*add)(int, int) = dlsym(handle, "add");
int (*subtract)(int, int) = dlsym(handle, "subtract");
if (!add || !subtract) {
fprintf(stderr, "Error: %s\n", dlerror());
dlclose(handle);
return 1;
}
// 调用函数
printf("Add: %d\n", add(3, 2));
printf("Subtract: %d\n", subtract(3, 2));
// 关闭动态库
dlclose(handle);
return 0;
}
创建和安装共享库
在 Linux 系统中,创建动态库后,可以安装到系统的标准路径中,供所有程序使用 (开发中常用) 。
1 将动态库复制到标准路径:
- 复制动态库到
/usr/lib/
或/usr/local/lib/
:
sudo cp libmath.so /usr/local/lib/
- 更新动态库缓存:
sudo ldconfig
2 配置动态库路径:
- 如果动态库不在标准路径中,可以通过设置
LD_LIBRARY_PATH
环境变量指定路径:
export LD_LIBRARY_PATH=路径:$LD_LIBRARY_PATH
- 在
main.c
中调用动态库时:
./main
二者的区别
静态库:
- 在编译阶段将库代码直接嵌入到程序中,适合独立运行的程序。
- 优点:运行时不依赖外部库,加载快。
- 缺点:文件体积大,更新麻烦。
动态库:
- 在运行时由操作系统加载,多个程序可以共享一个动态库。
- 优点:节省存储空间,易于更新。
- 缺点:运行时依赖,启动稍慢。
特性 | 静态库 | 动态库 |
---|---|---|
文件扩展名 | .a(Linux), .lib | .so(Linux), .dll |
链接方式 | 编译时链接 | 运行时加载 |
运行依赖 | 无需额外依赖 | 程序运行时需要动态库 |
文件大小 | 较大(包含库代码) | 较小(共享库代码) |
更新成本 | 高(需重新编译) | 低(更新库即可) |
加载速度 | 快 | 较慢(需动态加载) |
额外附加相关的一些 GCC 操作选项:
-shared:指定生成动态链接库。
-fPIC:表示编译为位置独立的代码,用于编译共享库。目标文件需要创建成位置无关码,概念上就是在可执行程序装载它们的时候,它们可以放在可执行程序的内存里的任何地方。
-w1,options:把参数(options)传递给链接器1d。如果options中间有逗号,就将options分成多个选项,然后传递给链接程序。
-static:指定生成静态链接库。
-c:只激活预处理、编译和汇编,也就是把程序做成目标文件(.o文件)。
-E:使用-E选项可以让GCC停止在预处理完成阶段(完成所有#define,#if,#include等替换),输出到标准输出,除非指定-0选项,qcc不再做更多的处理。可使用-0选项生成一个以i结尾的文件(GCC默认将.i文件看成是预处理后的C语言源代码)
-S:停止在汇编阶段,输出.s(汇编语言源码)但不调用as
最后,在实际开发中,我们对于静态库或动态库的选择取决于应用场景:
- 静态库适用于单机程序或对性能要求高的场景。
- 动态库适用于需要共享代码、频繁更新的场景。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!