文章目录
- 一、静态库(Static Library)
- 二、动态库(Dynamic Library)
- 三、静态库和动态库的比较
- 四、静态库的制作与使用
- 五、动态库的制作与使用
- 六、如何区分链接的是动态库还是静态库
在Linux系统编程中,库是一组预先编写好的代码,可以在多个程序中使用。根据链接方式不同,库可以分为静态库和动态库。下面详细介绍这两种库以及它们的使用方法。
一、静态库(Static Library)
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为 .a 。这类库的名字一般是libxxx.a,xxx是静态库的名字,lib是静态库的前缀。
利用静态库编译成的文件比较大, 因为库会被整合进目标代码中,它的优点就显而易见了,即编译后的执行程序不需要外部的库支持,因为所有使用的库函数都已经被编译进可执行文件。当然这也会成为它的缺点,因为如果静态库变了,那么你的程序必须重新编译,而且体积比较大。
静态链接库的特点
(1)、编译时链接
编译时链接:在编译时,库代码被嵌入到可执行文件中。编译器和链接器将库文件中的目标代码与应用程序的目标代码进行整合,生成单一的可执行文件。
独立性:生成的可执行文件可以独立运行,无需依赖库文件的存在。
(2)、生成单个大文件
文件体积较大:由于库代码被嵌入到可执行文件中,生成的可执行文件体积较大,包含了所有必需的库代码。
(3)、分发和部署
易于分发:由于所有依赖库都已经包含在可执行文件中,分发和部署相对简单,不需要额外的库文件。
无库版本兼容问题:避免了因为库版本不同而造成的兼容性问题。应用程序总是运行特定版本的库代码,确保了应用程序行为的一致性。
(4)、加载时间
更快的加载时间:由于所有必需的代码都在可执行文件中,加载可执行文件时无需额外的动态链接步骤,启动速度相对较快。
(5)、运行时内存消耗
独立内存消耗:每个使用静态库的可执行文件在运行时都会占用独立的内存,这可能会导致内存使用效率较低,特别是在多个应用程序使用同一个静态库时。
(6)、更新和维护
更新麻烦:如果需要更新库代码,必须重新编译所有依赖该库的应用程序。每次更新库都需要重新编译和分发整个应用程序。
安全性和稳定性:不容易受到外部库更新或系统环境变化的影响,程序行为稳定且可预测。
(7)、库代码优化
编译器优化:静态链接过程中,编译器可以对整个程序进行全局优化,包括库代码和应用程序代码一起进行优化,可能生成更高效的机器代码。
(8)、无库搜索路径问题
无需配置库路径:由于所有代码都嵌入到可执行文件中,不需要在运行时配置库的搜索路径,不会遇到找不到库文件的问题。
(9)、调试和诊断
调试方便:所有代码包括库代码都在一个文件中,调试工具可以直接访问所有代码,有助于调试和诊断问题。
二、动态库(Dynamic Library)
动态库(Dynamic Library),也叫共享库(Shared Library),是一种在运行时加载的库。它们的文件扩展名通常是 .so(共享对象)。这类库的名字一般是libxxx.so,有时候也会看到一些开源库命名成libxxx.so.x.y.z,xxx是动态库的名字,lib是动态库前缀,x是主版本号,y是次版本号,z是发布版本号。
相对于静态库,动态库在编译的时候并没有被编译进目标代码中,而是在程序运行时才被载入,因此动态库所产生的可执行文件比较小。由于动态库没有被整合进目标代码中,而是程序运行时动态的加载并调用,所以程序的运行环境必须提供相应的库。动态库的改变并不影响你的程序,所以动态库的升级比较方便。而且如果多个程序都要使用同一个函数,动态库就非常适合,可以减小应用程序的体积。
动态链接库的特点
(1)、运行时链接
动态加载:动态链接库在程序运行时被加载,可以按需加载,提升应用程序的启动速度和灵活性。
灵活性:允许在程序运行时替换或更新库,而不需要重新编译和重新分发应用程序。
(2)、减少可执行文件体积
可执行文件小:由于不包含库的实现代码,可执行文件较小,主要包含符号表和链接信息。
共享代码:多个程序可以使用相同的动态库,从而减少总的存储需求。
(3)、共享内存和资源
内存共享:多个进程可以共享同一个动态库的内存实例,节省系统资源并提高效率。
资源高效:由于共享同一个库实例,可以显著减少内存使用量,特别是在大型应用程序和多程序环境中。
(4)、简化更新和维护
库独立更新:只需更新动态库文件,而无需重新编译依赖这些库的所有应用程序。
快速修复:应用程序一旦发现问题,可以通过更新动态库快速修复,而无需对应用程序进行大规模修改。
(5)、增强的灵活性
运行时决策:应用程序可以在运行时决定使用哪些库,实现更高的灵活性和适应性。
(6)、系统依赖管理
库路径管理:需要管理库文件的搜索路径,以确保在运行时能够正确加载所需的库文件。
环境变量:环境变量如 LD_LIBRARY_PATH可以用来指定库搜索路径,同时 ldconfig 提供了配置方便的库路径管理。
(7)、调试复杂性
默认路径:调试动态库相对复杂,因为需要确保调试符号可用,并且调试工具能够正确解析和加载动态库。
诊断工具:需要使用特定的工具(如ldd)来检查动态库依赖关系,并使用工具(如 strace)来追踪库加载过程。
(8)、性能影响
启动时间稍慢:由于需要在程序启动时加载和链接动态库,启动时间可能会略微增加,但通常影响较小。
运行时效率:如果管理得当,动态库可以提升系统整体资源利用效率,但需要处理好潜在的符号解析和加载开销。
三、静态库和动态库的比较
链接时间:
静态库:在编译时链接。
动态库:在运行时链接。
可执行文件大小:
静态库:可执行文件包含所有相关库代码,因此通常较大。
动态库:可执行文件只包含库的引用,较小。
内存消耗:
静态库:每个使用同一静态库的程序都有一份库代码拷贝。
动态库:多个进程可以共享同一份动态库的内存映像,因此总消耗较少。
更新和维护:
静态库:更新库时,需要重新编译所有依赖该库的程序。
动态库:更新库后,所有依赖该库的程序在下次启动时都会使用新库。
加载时间:
静态库:加载时间较短,因为所有代码已经在可执行文件中。
动态库:首次加载时需要额外的时间以加载动态库。
总结
静态库和动态库在Linux系统编程中各有优劣。静态库简单易管理,但消耗更多的磁盘空间和内存,适用于不频繁更新的库。动态库节省资源,适用于共享库更新频繁的场景,但需要注意库版本兼容性问题。
四、静态库的制作与使用
【1】源码
foo.c
#include <stdio.h>void foo()
{printf("This is function foo\n");return;
}
bar.c
#include <stdio.h>void bar()
{printf("This is function bar\n");return;
}
main.c
#include <stdio.h>
#include "test.h"int main(int argc, char *argv[])
{foo();bar();return 0;
}
【2】编译源代码文件为目标文件
gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
【3】创建静态库文件
使用 ar 命令将目标文件组合成一个静态库文件。
ar rcs libmylib.a foo.o bar.o
【4】链接静态库
链接静态库并编译可执行文件
gcc main.c -I. -L. -lmylib -o app
-I
: 后面接头文件
-L
: 后面接库文件路径路径
-l
: 后面接库文件名
ubuntu下demo运行示例如下:
五、动态库的制作与使用
【1】源码
源码同静态库源码
【2】编译源代码文件为目标文件
gcc -fPIC -c foo.c -o foo.o
gcc -fPIC -c bar.c -o bar.o
其中,-fPIC 表示生成位置无关代码。
【3】创建动态库文件
使用 gcc 命令将目标文件组合成一个动态库文件。
gcc -shared -o libmylib.so foo.o bar.o
【2】【3】步骤合为一句指令,如下:
gcc foo.c bar.c -fPIC -shared -o libmylib.so
【4】链接动态库
链接动态库并编译可执行文件
gcc main.c -I. -L. -lmylib -o app
-I
: 后面接头文件
-L
: 后面接库文件路径路径
-l
: 后面接库文件名
ubuntu下demo运行示例如下:
如果动态库不在标准库路径中,必须设置库路径。例如,如果库在当前目录,可以使用:LD_LIBRARY_PATH=. ./app
,或者将库路径添加到系统路径中。
六、如何区分链接的是动态库还是静态库
通过上面“静态库的制作与使用”和“动态库的制作与使用”我们看到链接库并编译执行文件都执行了命令
gcc main.c -I. -L. -lmylib -o app
根据这一句命令,如何区分链接的是静态库还是动态库呢
在编译和链接程序时,gcc 默认优先链接动态库(.so文件)而不是静态库(.a文件)。然而,也可以通过指定选项明确告诉编译器链接静态库或动态库。以下是具体的说明和方法:
链接静态库
如果你明确想要链接静态库,你可以使用 -static 选项。这将强制 gcc 使用静态库进行链接:
gcc main.c -I. -L. -lmylib -o app -static
//这里的-static选项是告诉编译器mylib是静态库,如果不用强制也可以用下面命令
//gcc main.c -I. -L. -lmylib -o app
这会通过添加 -static 选项来强制链接静态库 libmylib.a(假设该库存在于库搜索路径下)。
链接动态库
如果不加任何特别的选项,gcc 默认会优先链接动态库(.so文件),前提是该动态库存在于指定的库路径中。
gcc main.c -I. -L. -lmylib -o app
这条命令会根据搜索路径优先尝试链接 libmylib.so,如果未找到动态库,才会尝试链接 libmylib.a。
检查链接的库
你可以使用 ldd 命令检查最终生成的可执行文件链接了哪些库。ldd 将显示动态链接库的路径和信息:
ldd app
如果可执行文件是动态链接的,你会看到它列出了链接的动态库(libmylib.so 等)。如果没有列出特定库或只显示基本系统库,这表明某些库(如 libmylib.a)是静态链接的。
静态库链接的可执行文件,执行ldd app,结果如下:
动态库链接的可执行文件,执行ldd app,结果如下:
出现错误:libmylib.so => not found
错误原因:ldd提示找不到库文件,而库文件就在当前目录中。因为链接器ldd默认的目录是/lib和/usr/lib,如果放在其他路径也可以,需要让ldd知道库文件在哪里。
解决方法1:编辑/etc/ld.so.conf文件,在新的一行中加入库文件所在目录;比如笔者应加:/home/hsiao/work,执行命令
ldconfig
目的是用ldconfig加载,以更新/etc/ld.so.cache文件。
解决方法2:执行命令
export LD_LIBRARY_PATH=/home/hsiao/work
export LD_LIBRARY_PATH=/home/hsiao/work 这条命令用于设置环境变量 LD_LIBRARY_PATH,其作用是告诉运行时链接器(dynamic linker/loader)在 /home/hsiao/work 目录中查找动态库(共享库,即 .so 文件)。
总结
静态库链接:使用 -static 来强制链接静态库。
动态库链接:默认情况下,gcc 优先链接动态库。无需特别选项,如果动态库存在于搜索路径中,它会被默认使用。
检查链接结果:使用 ldd 命令检查生成的可执行文件链接了哪些库。