Linux动态库 vs 静态库:创建步骤与优缺点对比
Linux系列
文章目录
- Linux系列
- 前言
- 一、动静态库的概念引入
- 1.1 库的基本概念
- 1.2 静态库(Static Library)
- 1.3 动态库(Dynamic Library)
- 1.4 动静态库的核心区别
- 二、动静态库的实现
- 2.1 静态库的创建及使用
- 2.2 动态库的创建和使用
- 三、优缺点对比
- 静态库
- 动态库
- 总结
前言
在Linux系统开发中,静态库和动态库是我们经常使用的两种库文件类型,使用库文件进行开发工作,可以大大提高我们的开发效率,学习动静态库的差异及使用方式是我们熟练运用它的前提。
本篇文章我们将通过自己制作动静态库,来深入的学习动静态的使用、差异、原理。
一、动静态库的概念引入
在我们学习语言的过程中,不可避免的会使用库函数,那么它到底是什么呢?又是如何实现的呢?
接下来我们会慢慢回答这个问题。
1.1 库的基本概念
库是用来实现代码共享的,是预编译的代码集合(.o文件的集合),用来提供可复用的函数、类或数据,从而简化我们的开发过程提高开发效率,当我们使用库中的代码时只需要对库链接就可以使用。库又分为动态库和静态库两种类型,这两者各有其特点。
1.2 静态库(Static Library)
静态库通常由多个.o
文件打包形成,以.a
为后缀的文件集合。当可执行程序链接静态库时,在程序编译阶段会将使用到的,库中的程序所在的.o
文件,全部嵌入至可执行文件中。
静态库的特点:
- 文件编译时会将程序所用到的代码所在的
.o
文件全部嵌入至可执行文件中。 - 每个可执行文件相对库来说是独立的(依赖代码已嵌入可执行文件)。
- 不需要在程序运行时进行额外的文件加载。
- 链接静态库会增加可执行文件的体积。
- 库文件更新时需要重新编译所依赖该库的可执行文件。
1.3 动态库(Dynamic Library)
动态库比较复杂,我会在本片后面,及其后续文章不断补充相关知识
动态库是在程序运行时再加载的共享库,当程序执行时通过链接器与动态库建立关系,这个过程并不会发生拷贝。动态库同样是以多个.o
文件打包的,以.so
为后缀的文件集。
动态库的特点:
- 程序运行时由操作系统动态加载、链接。
- 一份被加载到内存的动态库,可被多个程序共享,减少了内存、磁盘的空间消耗。
- 程序不需要额外内存,相较于链接静态库的可执行文件所占内存较小。
- 更行动态库不需要重新编译可执行文件。
1.4 动静态库的核心区别
特性 | 静态库 (.a) | 动态库 (.so) |
---|---|---|
链接方式 | 编译时直接嵌入到可执行文件中 | 运行时由动态链接器(如 ld-linux.so )加载 |
文件体积 | 可执行文件较大(包含库代码拷贝) | 可执行文件较小(仅记录引用) |
内存占用 | 每个进程独立加载库代码,内存冗余 | 多个进程共享同一份库内存 |
更新维护 | 需重新编译程序 | 替换 .so 文件后立即生效 |
依赖管理 | 无外部依赖,独立运行 | 需确保运行时库路径正确 |
加载速度 | 启动快(无需加载库) | 启动稍慢(需加载库) |
静态库: 静态库的链接发生在可执行程序的编译时。编译器会将可执行文件中所涉及库中的代码,对应的.o
文件嵌入到可执行文件中。
动态库: 动态库的链接发生在程序运行。动态库的链接会在程序启动时将动态库加载至内存的共享区,并库代码链接至可执行程序中(具体行为会在下篇文章中详细介绍)。
二、动静态库的实现
静态库和动态库本质都是.o
文件的集合,只是在应用层两者的库文件的打包方式及访问方式存在差异,下面我们使用简单的代码,感受库的创建及使用,来深入学习。
在此过程中我们会以库的制作者和使用者两个视角来感受
2.1 静态库的创建及使用
代码部分:
#pragma once //防止头文件被重复包含
#include
extern int myerrno;//标识该变量在源文件中已声明,具体作用可以搜一下
int mydiv(int x,int y);
上面代码为mymath.h
文件代码,学到这里我们就必须知道了,在我们编写代码时包含的.h文件
(头文件)其实就是相当于库函数的一份说明书,内部并不存在具体函数实现。
#include"mymath.h"
int myerrno=0;
int mydiv(int x,int y)
{
if(y==0)
{
myerrno=1;//防止除0错误
return -1;
}
return x/y;
}
上述代码为mymath.c
文件内容。
1 static-lib=libmymath.a
2 $(static-lib):mymath.o
3 ar -rc $@ $^
4 mymath.o:mymath.c
5 gcc -c $^
6 .PHONY:output
7 output:
8 mkdir -p mylib/include
9 mkdir -p mylib/lib
10 cp *.h mylib/include
11 cp *.a mylib/lib
12
13 .PHONY:clear
14 clear:
15 rm -rf *.o *.a mylib
上面代码为Makefile
文件内容,我来简单的解释一下:
2~3:使用ar -rc
指令将.o
文件打包成名为libmymath.a
的静态库。
6~12:模拟库的发布,供使用者使用(Linux中也存在,存储库及头文件的目录)。
创建出我们的静态库并将库发布。
使用者将别人的库安装到自己所在路径。
#include
int main()
{
int tmp=mydiv(10,1);
printf("%d
",tmp);
return 0;
}
上面为main.c
文件内容。
注意到了这里我们的库还是无法做到,像使用系统中的默认库那样使用,当你编译此文件时会发生链接错误,当我们的程序执行时找不到mymath.h
的内容,这点很容易理解,他们都不在同一路径下,那么为什么c语言的库函数就可以直接编译呢?在Linux
系统中,当我们在指向一个程序时,系统默认会去/usr/include/
路径下寻找程序所包含的头文件,会去/lib64/
路径下去找程序所需要的库文件,所以要想我们的库可以像c库一样被使用,只需将我们所写的libmymath.a
库和mymath.h
拷贝到上述系统路径即可,这里我们就不演示了,下面我们介绍另一种方法:
gcc 目标文件 -I 指定头文件搜索路径 -L 指定库文件搜索路径 -l指定链接库文件
gcc main.c -I mylib/include/ -L mylib/lib/ -lmymath
这里需要注意的是,当我们指定库文件是,只需要库文件名即可:lib库文件名.a。
程序成功编译并执行,此时a.out
可执行文件中,已经嵌入一份mymath.o
文件的数据了,即使你将库删除文件仍能执行,这里我就不演示了。
2.2 动态库的创建和使用
这里我们着重讲解两者不同的部分
代码部分:
mymethod.h
文件内容:
1 #pragma once
2 #include<stdio.h>
3 int print(char *str);
mymethod.c
文件的内容:
1 #include"mymethod.h"
2
3 int print(char *str)
4 {
5 printf("%s
",str);
6 return 0;
7 }
Makefile
文件的内容:
1 dy-lib=libmymethod.so
2 $(dy-lib):mymethod.o
3 gcc -shared -o $@ $^
4 mymethod.o:mymethod.c
5 gcc -fPIC -c $^
6 .PHONY:output
7 output:
8 mkdir -p mylib/include
9 mkdir -p mylib/lib
10 cp *.h mylib/include
11 cp *.so mylib/lib
12 .PHONY:clear
13 clear:
14 rm -rf *.o *.so mylib
2~3:将.o
文件打包变为动态库,与静态库的打包方式不同。
4~5:将.c
文件编译为.o
文件
-fPIC:生成位置无关码,会在下篇介绍
接下来我们直接将库生成,并发布,以使用者角度再次探讨:
1 #include<mymethod.h>
2
3 int main()
4 {
5 print("abcdefg");
6 return 0;
7 }
现在我们使用程序链接静态库一样的方式,链接了动态库并生成了可执行文件,但是如果我们仅做了上面这些行为,生成的可执行程序还是无法执行,这是因为不同于静态链接的行为(将库嵌入到可执行文件),动态链接在链接时只是记录依赖关系,真正的加载是在原型时。所以即使用户在编译时正确指定了-L
和-l
选项,生成的可执行文件在运行时仍然找不到动态库。
解决程序找不到动态库的方法:
1、将库文件和头文件拷贝到系统默认的库路径/lib64
和/usr/include
下(系统默认链接搜索路径)。
2、在上述路径下与我们的库文件建立软连接(软连接的相关只是,上篇我们)。
3、将自己的库所在路径,添加到系统的环境变量LD_LIBRARY_PATH
中。
4、在/etc/ld.so.conf.d
路径下建立自己的动态路径的配置文件,然后重新ldconfig
即可。
上述方法大家可以自己尝试,这里就不做展示了
三、优缺点对比
静态库
- 优点:
- 无运行时依赖,部署简单。
- 启动速度快(无需加载库)。
- 缺点:
- 可执行文件体积大,浪费磁盘和内存。
- 更新需重新编译,维护成本高。
动态库
- 优点:
- 节省磁盘和内存资源。
- 支持热更新(替换
.so
文件即可)。
- 缺点:
- 依赖管理复杂(需设置
LD_LIBRARY_PATH
或rpath
)。 - 版本冲突风险(如
libfoo.so.1
和libfoo.so.2
不兼容)。
- 依赖管理复杂(需设置
总结
本篇文章我们就介绍到这里,需要补充的是,gcc
在编译程序时默认链接动态库,如果想要链接静态库可以使用-static
显示指定。对于动态如如何实现共享,以及如何加载到内存这点我会在下篇介绍。