目录
一、库
在正式讲解动态库与静态库之前,我想先和大家聊一聊库。库是什么呢?简单来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。我们通常将一些通用函数写成函数库,所以库是别人写好的,我们只要遵守许可协议就可以使用,但是我们不能看到库是如何实现的。
那么库为什么会有这种特性呢?因为库文件里面一般是一种技术的核心代码。为了将这种技术出售出去,供别人使用,但又不希望泄露自己的技术。于是便赋予了库这个特性。这也是我们为什么学习库的原因。
在实际开发过程中,不可能每一份代码都从头编写。如果我们拥有了库,我们就可以直接将我们所需要的文件链接到我们的程序中。这样可以为我们节省大量时间,提高开发效率。
Linux下库分为两种,分别是静态库和动态库。这两种库都是由.o文件生成的,但是他们在不同的操作系统命名是不同的,让我们来看看区别吧!
Windows | Linux | |
静态库 | "libxxx.a" | "libxxx.a" |
动态库 | "libxxx.dll" | "libxxx.so" |
那么在Linux下,库在哪里呢?库主要处于/lib和/usr/lib目录中。两者的区别在于:/lib指系统运行相关的库文件;/usr/lib一般指第三方软件的库文件。
了解了库的相关知识,就叫我们一起来看看动态库和静态库的知识吧!
二、静态库(Static Library)
静态库是一组已经编译好的目标文件(Object File)的集合,它们被打包成一个单独的文件供开发者使用。静态库在链接时会被完整地复制到生成的可执行文件中,因此可执行文件独立于外部环境,不需要额外的运行时支持。
(1)优点
- 可移植性强: 由于静态库已经包含了所需的目标代码,因此在不同的系统和平台上使用时无需考虑外部依赖。
- 性能优化: 静态库在编译时与可执行文件链接,可以进行更多的优化,提高程序执行效率。
- 版本控制: 静态库的版本控制相对简单,每个版本都是一个独立的文件,开发者可以灵活管理和控制版本更新。
(2)缺点
- 用空间: 每个可执行文件都包含了静态库的副本,可能导致可执行文件的体积较大。
- 更新维护困难: 如果静态库有更新,需要重新编译并重新链接所有依赖于它的可执行文件。
(3)生成静态库文件
如果想要源代码做成静态库文件,可以如下指令:
gcc -c file1.c
gcc -c file2.c
ar -rcs libname.a file1.o file2.o
三、动态库(Dynamic Library)
动态库是一组已编译好的目标代码,但它们在程序执行时才会被加载到内存中。动态库可以被多个程序共享,因此可以节省内存空间,并且可以实现库的热更新和动态链接。
(1)优点
- 节省内存: 多个程序可以共享同一个动态库的实例,减少了内存占用。
- 易于更新: 可以直接替换动态库文件而不需要重新编译依赖它的程序。
- 共享资源: 多个程序可以共享动态库中的代码和数据,提高了代码重用性。
(2)缺点
- 运行时依赖: 程序在执行时需要动态库的支持,如果缺少相应的动态库文件,会导致程序无法运行。
- 版本兼容性: 动态库的版本兼容性需要特别注意,不同版本的动态库可能存在不同的接口和行为。
(3)生成动态库文件
如果想要源代码做成静态库文件,可以如下指令:
gcc -shared -fPIC -o libname.so file1.c file2.c
四、应用场景
-
静态库应用场景:
- 在需要独立部署的应用中,或者对程序的性能有较高要求时,可以选择静态库。
- 对于不频繁更新的代码,静态库可以提供更稳定的版本控制。
-
动态库应用场景:
- 在需要共享代码和节省内存的场景中,可以选择动态库。
- 对于经常需要更新的库文件,特别是在大型项目中,动态库可以提供更灵活的更新和部署方式。
五、实战演练
假想一个场景,甲公司研究了多年,终于研究出了该算法。现在甲公司想要用该算法进行商业盈利,很显然甲公司并不会把源代码直接给客户。那怎么办呢?甲公司可以把这些源代码编译成库文件的形式提供给客户使用。假设该算法只有一个C文件calculate.c和calculate.h,其源代码如下:
vim calculate.c /*编辑源代码*/
/*calculate.c*/
#include <stdio.h>
int sum(int parameterA,int parameterB)
{
int sum = 0;
sum = parameterA + parameterB;
return sum;
}
int multiply(int parameterA,int parameterB)
{
int mul = 0;
mul = parameterA * parameterB;
return mul;
}
vim calculate.h /*编辑头文件*/
/*calculate.h*/
#ifndef _CALCULATE_H_
#define _CALCULATE_H_
/*Description:This function used to add two numbers */
extern int sum(int parameterA,int parameterB);
/* Description:This function used to multiply two numbers */
extern int multiply(int parameterA,int parameterB);
#endif
对于甲公司,可以使用如下命令将源代码分别生成静态库libmycalculate.c和动态库libmycalculate.so提供给客户。
/*生成静态库文件libmycalculate.c*/
gcc -c calculate.c
ar -rcs libmycalculate.a *.o
/*生成动态库libmycalculate.so*/
gcc -shared -fPIC calculate.c -o libmycalculate.so
现在甲公司已经将源代码编译成静态库libmycalculate.c和动态库libmycalculate.so,此时将静态库文件、动态库库文件和calcuate.h放到同一个文件里(我这里在该目录下新建了一个lib文件夹),所以客户只能获得静态库文件、动态库库文件和calcuate.h,并不会获得甲公司的源代码。如图所示:
前文已经提到了,我们只要遵守许可协议就可以使用。那现在一个客户需要用该算法怎么办呢?让我们一起来操作一下吧!
mkdir test /*创建一个文件夹*/
vim main.c
/*用户利用该算法进行编写程序main.c*/
#include <stdio.h>
#include "calculate.h"
int main(int argc,char *argv[])
{
int a = 3;
int b = 4;
int s = 0;
int m = 0;
s = sum(a,b);
printf("the result of adding two numbers is:%d\n",s);
m = multiply(a,b);
printf("the result of multipling two numbers is:%d\n",m);
return 0;
}
那客户现在直接编译能运行吗?叫我们一起来试试吧:
gcc main.c -o test
看来,是不能直接运行的。为什么会出现这种错误呢?错误提示是不能找到calcuate.h这个文件。这是因为该客户编写的程序与该头文件不在同一路径下。那要怎么解决呢?
最简单直接的办法就是把头文件直接复制粘贴一份,移动到该路径下。不过这样也会占更多的内存。其实,我们可以使用编译器的-I(大写)选项来指定头文件的路径。命令如下:
gcc main.c -o test -I ../lib
(注:在这条指令中,我用到了绝对路径,对该知识不熟悉的同学,推荐看这篇文章-《绝对路径与相对路径》)
诶?怎么编译还有错误呢?错误原因是我们调用的函数未被定义。这是因为我们的链接器会默认到系统动态库路径(/lib、/usr/lib)下查找相应的库文件,如果找不到就报错。
我们可以使用编译器的-L选项来指定相应库的路径,我们常用如下的命令进行编译:
gcc 源文件 -o 目标文件 -I 头文件路径 -L 库文件路径 -lname
如果静态库和动态库同时存在,系统则优先使用动态库。如果想要使用静态库链接,我们可以使用如下命令:
gcc 源文件 -o 目标文件 -I 头文件路径 -L 库文件路径 -lname -static
学了以上命令,那我们就将客户刚刚编写的main.c文件分别进行静态链接和动态链接吧!
/*静态链接*/
gcc main.c -o test_static -I ../lib -lmycalculate -L ../lib -static
/*动态链接*/
gcc main.c -o test -I ../lib -lmycalculate -L ../lib
生成后,如图所示:
最后,叫我们分别来运行一下程序吧!
/*运行静态链接的可执行文件test_static文件*/
./test_static
我们可以看到,客户编写的程序可以成功运行啦!
/*运行动态链接的可执行文件test文件*/
./test
诶?为什么动态链接生成的可执行文件无法正常运行呢,而静态链接生成的可执行文件就能够正常运行呢?让我们一起带着疑问来思考一下吧!
我们可以使用如下命令进行查看:
ldd 文件名
通过该图可以看到,Linux运行程序时,在默认的路径没有查找到所需的动态库文件,所以报错。
回到刚刚的问题上,我来解答一下。这是因为静态编译的程序,所有的代码段和数据段都被链接进可执行程序中,所以可以直接执行;而动态编译的程序,动态库中的代码段和数据段并没有被链接进可执行程序中,只是记录了需要他们的一些信息。所以在运行时需要操作系统帮忙加载这些动态库程序,直接运行就会出错。
那要怎么解决该问题呢?通常有两种方法来解决:
(1)将所需要的动态库文件拷贝到usr/lib路径下,当然这需要root权限;
(2)使用export命令在LD_LIBRARY环境变量中添加该动态库所在的路径,注意该命令只是临时生效,重启后会失效。
第二种方法使用如下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库文件所在的绝对路径(注意,必须是绝对路径)
此时在运行动态编译的可执行文件:
好啦,到这里,静态链接生成的可执行文件和动态链接生成的可执行文件都能够正常运行了。
静态库和动态库各有优缺点,开发者在选择使用时需要根据具体的项目需求和运行环境进行权衡。在实际开发中,有时也会同时使用静态库和动态库,以满足不同的需求。通过深入理解静态库和动态库的特性及其应用场景,开发者可以更好地选择适合自己项目的库文件形式,从而提高开发效率和程序性能。