计算机基础(六):静态链接与动态链接
上一篇文章简单概括了 C语言程序经过编译,生成汇编语言、机器语言的基本过程。今天主要介绍其中链接阶段的实现思路。
静态链接
静态链接是将被依赖的代码片段复制到执行程序中,进行代码整合。因为我们在汇编代码中看到的是具体的符号,而且每个代码片段都有自己的地址信息,在整合静态代码库的时候,就需要进行对应的符号解析与重定位。
#include <stdio.h>
int main()
{
printf("Hello, World! \n");
int res = add(1, 1);
printf("add result is: %d \n", res);
return 0;
}
这里调用的add函数是在另一个代码文件 utils.c 中定义的,如果我们直接编译demo.c 则直接报告一个异常:
/tmp/cc3CdEzz.o: In function `main':
demo.c:(.text+0x1e): undefined reference to `add'
collect2: error: ld returned 1 exit statu
此时我们定义另一个文件 utils.c:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
执行编译:
gcc demo.c utils.c
那么将两段代码进行整合时,会将 utils.c 编译后的代码复制到输出文件中,这样执行时自然就能找到依赖的add函数了。
静态链接理解起来并不复杂,其优点是:1. 整个程序里包含了所有的代码,移植到其他机器上不需要安装别的库;2. 由于不需要动态解析,所以执行很快。缺点则更加明显:1. 空间冗余,所有代码在内存中发生冗余;2. 一旦静态链接库发生变化,那么需要将所有依赖静态链接库的代码重新编译一遍。也因此,出现了动态链接来解决这个问题。
动态链接
动态链接库可以被多个运行的程序进行共享,代码不需要每个依赖的程序都拷贝一遍。这样就需要将动态链接库映射到每个运行的程序中才会被执行,又因为映射到每个程序中的地址是不同的,因此需要对动态链接库进行单独的识别与解析。基本原理如下所示:
基本使用
gcc -shared -o libutils.so utils.c #生成动态链接库 libutils.so
gcc demo.c libutils.so #编译
此时如果执行 a.out 的话会出错:
./a.out: error while loading shared libraries: libutils.so: cannot open shared object file: No such file or directory
需要将当前路径导出到动态链接库的访问列表中:
export LD_LIBRARY_PATH=.$LD_LIBRARY_PATH
此时再执行a.out的时候才会成功。
基本原理
程序A和B中都依赖了同一个动态链接库中的某全局数据或函数,因此两者访问库中的数据或函数时,需要加载到自己的程序中执行。但因为地址不一样,需将地址进行映射到对应自己的程序中才能访问。那到底是如何实现映射的呢,这就涉及到ELF中相关的规范了。
ELF
ELF全称是:Executable and Linkable Format
我们的代码被编译、汇编为二进制文件,必须遵循着特定的结构,其中最常见的一种就是 Executable and Linkable Format 的格式。 它广泛用于可执行文件、可重定位目标文件、共享库中。
未完待续……