大家好,欢迎来到IT知识分享网。
静态链接与动态链接
静态链接与动态链接
静态链接
为什么要进行静态链接?
静态链接的原理

值得注意的是,从上面的图片中可以看到静态链接库里面的一个目标文件只包含一个函数,如libc.a里面的printf.o只有printf()函数,strlen.o中只有strlen函数。
我们知道,链接器在链接静态链接库的时候是以目标文件为单位的。比如我们引用了静态库中的printf()函数,那么链接器就会把包含printf()函数的那个目标文件链接进来。如果很多函数都放在一个目标文件中,很可能很多没用的函数都被一起链接进了输出结果中。由于运行库中有成百上千个函数,数量非常庞大,每个函数独立地放在一个目标文件中可以尽量减少空间地浪费,那些没有用到地目标文件就不要链接到最终地输出文件中。
创建和使用静态链接库
Linux下的静态链接库是以.a结尾的二进制文件,它作为程序的一个模块,在链接期间被组合到程序中。
制作链接库的目的是希望别人使用我们已经实现的功能,但又不希望别人看到我们的源代码,这对商业机构是非常友好的。
Linux下静态链接库文件的命名规则为:libxxx.a,xxx 表示库的名字。例如,libc.a、libm.a、libieee.a、libgcc.a都是 Linux 系统自带的静态库。
生成静态链接库
- 首先使用
gcc命令把源文件编译为目标文件,也即.o文件:
gcc -c 源文件列表
-c选项表示只编译,不链接 - 然后使用
ar命令将.o文件打包成静态链接库,具体格式为:
ar rcs libxxx.a 目标文件列表
ar是Linux的一个备份压缩命令,它可以将多个文件打包成一个备份文件(也叫归档文件),也可以从备份文件中提取成员文件。ar命令最常见的用法是将目标文件打包为静态链接库。
对参数的说明:- 参数
r用来替换库中已有的目标文件,或者加入新的目标文件。 - 参数
c表示创建一个库。不管库否存在,都将创建。 - 参数
s用来创建目标文件索引,这在创建较大的库时能提高速度。
- 参数
实例演示
任选一个目录,创建一个文件夹 test,将 test 作为整个项目的基础目录。在 test 目录中再创建四个源文件,分别是 add.c、sub.c、div.c 和 test.h。代码如下:
// test.h
#ifndef __TEST_H_
#define __TEST_H_
int add(int a,int b);
int sub(int a,int b);
int div(int a,int b);
#endif
// add.c
#include "test.h"
int add(int a,int b) {
return a + b;
}
// sub.c
#include "test.h"
int sub(int a,int b) {
return a - b;
}
// div.c
#include "test.h"
int div(int a,int b) {
return a / b;
}
首先将所有源文件都编译成目标文件:
gcc -c *.c
*.c表示所有以.c结尾的文件,也即所有的源文件。执行完该命令,会发现 test 目录中多了三个目标文件,分别是 add.o、sub.o 和 div.o。
然后把所有目标文件打包成静态库文件:
ar rcs libtest.a *.o
*.o表示所有以.o结尾的文件,也即所有的目标文件。执行完该命令,发现 test 目录中多了一个静态库文件 libtest.a,大功告成。
我们也可以通过以下命令查看静态链接库内的文件:
ar -t ./lib/libtest.a
输出:
使用静态链接库
使用静态链接库时,除了需要库文件本身,还需要对应的头文件:库文件包含了真正的函数代码,也即函数定义部分;头文件包含了函数的调用方法,也即函数声明部分。
为了使用上面生成的静态链接库libtest.a,我们需要启用一个新的项目。任选一个目录,创建一个文件夹math,将math作为新项目的基础目录。
在比较规范的项目目录中;lib 文件夹一般用来存放库文件;include 文件夹一般用来存放头文件;src 文件夹一般用来存放源文件;bin 文件夹一般用来存放可执行文件。
为了规范,我们将前面生成的 libtest.a 放到 math 目录下的 lib 文件夹,将 test.h 放到 math 目录下的 include 文件夹。
在 math 目录下再创建一个 src 文件夹,在 src 中再创建一个 main.c 源文件。
此时 math 目录中文件结构如下所示:
在 main.c 中,可以像下面这样使用 libtest.a 中的函数:
#include <stdio.h>
#include "test.h" //必须引入头文件
int main() {
int m, n;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
printf("%d÷%d=%d\n", m, n, div(m, n));
return 0;
}
在编译 main.c 的时候,我们需要使用-I(大写的字母i)选项指明头文件的包含路径;使用-L选项指明静态库的包含路径;使用-l(小写字母L)选项指明静态库的名字。所以,main.c 的完整编译命令为:
gcc src/main.c -I include/ -L lib/ -l test -o math
注意,使用-l选项指明静态库的名字时,既不需要lib前缀,也不需要.a后缀,只能写 test,GCC 会自动加上前缀和后缀。
打开 math 目录,发现多了一个 math 可执行文件,使用./math命令就可以运行 math 进行数学计算。
静态链接的优缺点
- 优点
- 由于在可执行文件中已经具备所有执行文件所需要的东西,在执行的时候运行速度快。
- 缺点
- 浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件有依赖,如多个程序中都调用了
printf()函数,则这多个程序中都包含printf.o,所以同一个目标文件在内存中存在多个副本。 - 更新困难,因为每当库函数的代码发生修改,这个时候就需要重新编译所有依赖这个库函数的程序,形成新的可执行文件。
- 浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件有依赖,如多个程序中都调用了
动态链接
为什么会出现动态链接?
动态链接出现的原因是为了解决静态链接中存在的两个问题:空间浪费和更新困难。
动态链接的原理
动态链接的基本思想是把程序按照模块拆分为各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接那样在链接时将所有的模块组成一个可执行文件。
假设现在有两个程序 program1.o 和 program2.o ,这两者共用同一个库 libxxx.o ,假设首先运行程序 program1 ,系统首先加载 program1.o ,当系统发现 program1.o 中用到了 libxxx.o ,即 program1.o 依赖于 libxxx.o ,那么系统接着加载 libxxx.o ,如果 program1.o 和 libxxx.o 还依赖于其他目标文件,则依次全部加载到内存中。当 program2 运行时,同样的加载 program2.o ,然后发现 program2.o 依赖于 lib.o ,但是此时 lib.o 已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的 libxxx.o 映射到 program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。
创建和使用动态链接库
创建动态链接库
我们用同样的代码来创建生成动态链接库:
gcc -shared -fPIC *.c -o libtest.so
shared:标志着要生成动态链接库fPIC:告诉编译器产生与位置无关的代码
这样,我们就生成了想要的动态链接库libtest.so
使用动态链接库
首先,我们需要生成可执行文件,此时的命令与静态链接相同:
gcc src/main.c -I include/ -L lib/ -l test -o math
此时,如果你直接使用./main执行程序,你会遇到这样的报错信息:
…/bin/math: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
LD_LIBRARY_PATH="./lib" ./bin/math
LD_LIBRARY_PATH=: 告诉链接程序,从指定的目录下寻找链接库。
动态链接的优缺点
- 优点
- 节省空间资源:共享同一个依赖库
- 更新便利:只需替换相关的模块即可
- 缺点
- 性能受损:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。 据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。
动态链接地址是如何重定位的呢?
虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。
更多:
如果想了解更多动态链接的知识,例如如何在 Linux 中通过 dlopen函数加载动态链接库,python如何使用C/C++编写的动态链接库等知识,详见参考3:深入浅出:Linux C编程中如何使用动态链接库。
参考:
- 深入浅出静态链接和动态链接
- GCC创建和使用静态链接库
- 深入浅出:Linux C编程中如何使用动态链接库
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/116912.html