以一段代码为例
#include<stdio.h> int func(); int main() { printf("Hello %d", func()); return 0; }
此处用到了一个函数printf(),这个函数一般放在<stdio.h>中,下面来具体介绍静态链接和动态链接过程。
首先需了解,静态链接是在程序运行前形成的。
我们知道,<stdio.h>这个标准库中一般包括很多个函数,如printf、fprintf、gets等等,而静态链接的时候他进行链接的是一整个<stdio.h>,而且静态链接的特点在于他是直接把这个对象文件加入到了可执行文件当中,极大的浪费了内存空间。
除此之外,如果当一个函数要求更改的时候,这就要更改所有的可执行文件,更新比较困难。
当然,优点是可执行程序中已经具备了所有执行程序所需要的东西,在执行的时候运行速度快。
动态链接字如其名,其链接过程是动态的在可执行程序执行的时候进行链接的。
也是按照上面代码的栗子,其调用了<stdio.h>中的printf()函数当生成可执行文件的时候其中的函数都是一个无符号地址(假地址),当程序执行的时候(加载器把文件复制进内存)会在.interp段解析出一个ld-linux.so.6动态链接库解释器,这个解释器是一个路径,会把用得到的动态链接库找到,加载器接着把该动态链接库加载进内存,可执行程序通过PLT(过程链接表)表和GOT(全局定位表)表重定位找到动态链接库中函数的位置并进行链接(一般采用的是延迟绑定技术)。
因此,动态链接极大地解决了内存占用过大和更新比较困难的问题。
当然,他也有一定的缺点,那就是每次执行程序都需要进行连接,所以性能会有一定的损失。
除此之外,再谈一谈符号解析,我们会在编译的过程中对象文件(.o)会程序一个符号表,其中包含了不包括局部元素和动态链接的变量和函数等等,当链接器(ld)进行链接的时候,会将每个引用与他输入的可重定位目标文件(.o)的符号表中的一个确定的符号定义关联起来,这和静态链接类似(也可以说就是)。
在传统的形成可执行程序过程中,重定位一般是在对象文件链接形成可执行文件的过程中的(对象文件中地址分配一般是根据内存映像来的,所有的段都是从地址0开始的,因此在放入内存中要进行内存映射),一般是根据重定位条目是根据.rel.text和.rel.data段来找的,具体过程不在此叙述,这是传统的方法。
而在动态链接过程中,在对象文件生成可执行文件的过程中,某些函数他是找不到的,如果检查动态链接库发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位(随便给一个地址),而把这个过程留到装载时再进行。