【IO编程】静态库 和 动态库

在软件开发中, 是一组已编译的代码集合,提供了程序可以直接调用的功能模块(如数学运算、字符串处理、文件操作等)。库的主要作用是提高代码复用性、减少重复开发,并提供标准化功能。

什么是库

库(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)完成库的合并。

动态库的加载

动态库的加载可以分为两种方式:

  1. 自动加载
    • 在程序启动时,操作系统根据程序的依赖自动加载动态库。
    • 依赖库路径可以通过 LD_LIBRARY_PATH 环境变量指定(Linux)。
  2. 手动加载(动态加载)
    • 程序运行时显式加载动态库,使用库中的函数。
    • 在 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
二者的区别

静态库

  1. 在编译阶段将库代码直接嵌入到程序中,适合独立运行的程序
  2. 优点:运行时不依赖外部库,加载快。
  3. 缺点:文件体积大,更新麻烦。

动态库

  1. 在运行时由操作系统加载,多个程序可以共享一个动态库
  2. 优点:节省存储空间,易于更新。
  3. 缺点:运行时依赖,启动稍慢。
特性静态库动态库
文件扩展名.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

最后,在实际开发中,我们对于静态库或动态库的选择取决于应用场景:

  • 静态库适用于单机程序或对性能要求高的场景。
  • 动态库适用于需要共享代码、频繁更新的场景。

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫的小茶馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值