静态链接&&动态链接
学习编程,要对编译链接过程了然如胸。在链接阶段,有两种链接方式——静态链接和动态链接。
两者最大的区别在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时,下面来详细介绍这两种链接方式。
一、静态链接
-
使用静态链接的原因
在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个 * .c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接。
-
静态链接原理
由很多目标文件进行链接形成的是静态库,即静态库可以简单地看成是一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件。链接过程中,根据在源文件中包含的头文件和程序中使用到的库函数,如stdio.h中定义的printf()函数,在静态库libxxx.a中找到目标文件printf.o,然后将这个目标文件和我们需要编译的目标文件进行链接形成可执行文件。
-
静态链接的优缺点
一是浪费空间。链接器在链接静态链接库的时候是以目标文件为单位的,比如我们引用了静态库中的printf()函数,那么链接器就会把库中包含printf()函数的那个目标文件链接进来,如果很多函数都放在一个目标文件中,很可能很多没用的函数都被一起链接进了输出结果中。由于运行库有成百上千个函数,数量非常庞大,每个函数独立地放在一个目标文件中可以尽量减少空间的浪费,那些没有被用到的目标文件就不要链接到最终的输出文件中。还有如果多个程序对同一个目标文件都有依赖,比如多个程序中都调用了printf()函数,则内存中存在同一个目标文件的多个副本,也会造成空间的浪费。
二是更新困难。因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
-
如何生成静态库
首先,你需要写几个你要调用的函数的源文件,如add.c、sub.c等。其代码如下:
//calc.h
#ifndef _CALC_H
#define _CALC_H
int add (int a,int b);
int sub(int a,int b);
#endif
//add.c
#include "calc.h"
int add(int a, int b){
return a+b;
}
//sub.c
#include "calc.h"
int sub(int a, int b){
return a-b;
}
再将头文件写好(其实这里的头文件并不需要,若是编译成库的程序之间有调用关系的时候才用得到,只在调用链接库的时候编写头文件即可)。开始编译,将源文件编译成.o文件。命令行指令如下:
gcc -c add.c sub.c
然后使用ar工具生成a库,指令如下:ar命令详细介绍可以参考这篇博客:
https://blog.csdn.net/freeman125384977/article/details/6697278
ar rcs libtest.a add.o sub.o //注意:链接库前缀必须以lib开头
得到libtest.a库文件后,然后将.a库链接到主程序中,写主程序main.c
//main.c
#include "calc.h"
#include<stdio.h>
int main(int argc, char const *argv[])
{
int a = 5, b = 3;
int c = add(a, b);
int d = sub(a, b);
printf("%d %d\n", c ,d);
return 0;
}
加载a库,生成可执行文件并执行,指令如下:
gcc main.c -L. -ltest -o test
可以看到,a库连接成功,可以调用到里面的函数。
二、动态链接
动态链接可以很好地解决静态链接的两个缺点。
-
原理
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:
假设现在有两个程序program1.o和program2.o,这两者共用同一个库lib.o,假设首先运行程序program1,系统首先加载program1.o,当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o,那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。
-
优缺点
- 即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本
- 更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标
- 缺点就是动态链接在程序运行阶段,所以每次执行程序时都要进行动态链接,效率有所损失。但是这点损失换取空间上的节省和程序构建和升级时的灵活性是划算的。
-
如何生成动态库
利用上面所写过的add.c 和sub.c生成.so库的指令如下:
gcc add.c sub.c -fPIC -shared -o libtest.so
同样so库的前缀必须为lib,然后将.so库链接到主程序main.c中,这里需要注意一下,因为动态库的特性,编译器会到指定的目录去寻找动态库,目录的地址在/etc/ld.so.conf.d/ 目录里的libc.conf文件里,你可以在里面加一行地址表示你so库的位置,更改完conf文件里的内容,记得输入命令行:ldconfig。
你还可以将so库复制到默认的目录下。这里是将so库复制到了默认目录 /uer/lib/ 下,生成可执行文件并运行,指令如下:
gcc mian.c -L. -ltest -o test
三、静态链接库和动态链接库的区别
主程序在运行前,静态链接库的链接固定写入在程序中,而动态链接库则是在每次程序运行再加载链接。
- 动态库是否加载到内存,取决于程序是否运行。
- 动态库每次加载的位置不固定。
- 动态库和静态库共存的时候,编译器默认使用动态库
在加载动态链接库的时候,有可能会遇到加载不到的错误,原因在于系统默认加载的动态链接库路径里没有找到你的动态库,有三种解决方法:
1). 在执行gcc main.c -L. -ltest -o main 前,在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径。即执行 export LD_LIBRARY_PATH=$(pwd)
2). 将你so所在的目录写到/etc/ld.so.conf文件里,然后执行ldconfig。
3). 将你的so放在/etc/ld.so.conf里的路径位置里。