前几天,在百度笔试题上遇到“动态链接库”与“静态链接库”两个概念。由于对编译、加载、链接这几个概念没有彻底弄清楚,所以当时写的答案现在看来距离正确的理解有比较大的偏差。这两天翻了下CSAPP,重新看了下“链接”的相关知识,这里做个总结,希望能借此进一步了解计算机程序运行的工作情况。
首先,计算机的编译系统由五个阶段,以C语言为例:
预处理阶段:主要根据以字符#开头的命令,修改原始的C程序。比如说#include <stdio.h>,预处理器就读取stdio.h中的内容,并直接插入到当前程序中去
编译阶段:这个阶段比较易于理解,就是将源代码翻译成汇编语言的格式
汇编阶段:这个阶段是将汇编语言翻译成机器语言指令,并把这些机器语言指令打包成“可重定位目标程序”的格式,就是常见的.o文件
链接阶段:C语言中经常需要用到printf这个函数,它是标准C库中的一个函数,但是以上阶段生成的目标文件中并不包含这个函数的定义,这个函数存在于一个名为printf.o的单独的目标文件中,链接阶段就负责将这些目标文件“并入”,得到最终的可执行文件
链接,概念上来说就是将不同部分的代码和数据组合成单一文件的过程,也就是并入不同目标模块的过程。它的主要作用是实现“分离编译”,因为每个目标模块编译是分开的,只是编译后链接到一起
链接的主要任务有两个:
符号解析:由于编译的每个模块都可能引入了其它模块中的变量(函数),所以将每个模块用到的变量/函数或定义的变量/函数都在编译时维护在符号表中,这样链接时候便可以根据符号表中的内容,将模块之间链接起来。经过链接符号解析后,每个模块对自己包含的符号(变量或者函数)都有了明确的解析,即使此符号的定义在其他模块中。
重定位:地址的重定位。
静态链接库:
静态库的概念就是提供一种机制,将所有相关的目标模块打包成一个单独的文件,称为静态库(注意,文件里面的每个目标模块都是独立的,都可以作为链接的目标,而不是这个文件可以作为链接的目标)。
举个例子,比如说所有“C语言的标准函数”,这些函数经常被程序引用,若想为程序提供这些函数,有以下方法:
1.将这些函数都放在一个单独的目标模块中,比如libc.o,然后每个程序执行前都与它链接即可。这个缺点很大,因为标准函数太多,势必导致libc.o很大,这样每个程序与它链接后,得到的可执行文件就很大,严重影响效率。
2.就是将每个标准函数作为一个单独的目标文件,比如说printf.o,scanf.o....这样虽然没有文件过大的缺点,但是每次需要用到哪个模块时候,都需要程序员显式指定链接哪个具体的模块,如果一个程序用到了很多的标准函数,那么就要显式提供一大堆这样的目标文件,将它们链接到可执行文件中去,显然也不方便。
静态链接库就是为了解决上述两个问题提出的方法,它将这些标准函数都作为单独目标模块,但是却将他们放在一个文件(或者称为归档)中,比如说libc.a,程序员在链接时,只需显式提供这个归档文件即可。
链接器会自动解析模块中用到了那些标准函数,并从归档文件中只拷贝用到的标准函数对应的目标模块,到最终的可执行文件即可。
然而静态链接库也有明显的缺点:它还是需要将用到的那些目标函数的模块拷贝到最终的可执行文件中去,如果10个程序都用到了printf,那么就需要从libc.a中拷贝10个printf.o的副本,链接到10个最终的可执行文件中,它的最大缺点是不能
”共享“
动态链接库(如dll和so)最大的特点就是共享,所以也称为共享库,对于任何文件系统,对于一个共享库只有一个.so或者.dll文件。
所有用到这个共享库的程序不需要拷贝实际的代码和数据到最终的可执行程序中去,而是只是拷贝一些重定位和符号表信息,使得程序在运行时可以解析对.so或者.dll文件中代码和数据的引用。