在从汇编的角度理解程序中我们了解到,程序主要是执行汇编指令来操作数据。这些汇编指令和大部分数据,除了其自身的值外,也都有固定的运行时内存位置。这些值和内存位置,由程序源码通过编译和链接而确定下来,并全部被保存在磁盘上的可执行文件中。操作系统把文件中的指令和数据值加载到内存中对应的位置,再把 PC 寄存器设置到 main 函数指令的开始内存位置,从而开始执行程序。
在编译时,源代码(.c
文件)首先通过预处理器来处理宏、注释等编程语言语法上的问题,形成 ASCII 中间件文件(.i
文件)。之后,编译器把编程语言翻译成 ASCII 码的汇编语言文件(.s
)。最后,汇编器把汇编语言文件翻译成二进制的可执行文件。
理论上来说,只需要编译这一个步骤就能确定汇编指令和变量的值和内存位置了,但是在实际工程中运行时内存位置却是在链接时最终确定的。更详细点说,就是每份源码单独编译成一个 ELF
文件(Executable and Linkable Format,意即可被执行又可被链接),其中只包含了该份源码本身的指令和变量的值,然后通过把所有的 ELF
文件链接在一起,为所有的指令和变量生成独特的运行时内存位置。
这样的好处是,当我们修改一个源代码文件后,只需要重新编译一个文件,再链接所有的文件。而且这种方式还附赠了一个便利:链接第三方库编译出来的 ELF 文件就能使用库提供的功能,免去了获取源码和编译源码的烦恼。
但是这些好处不是免费的,有两个明显的和内存位置相关的问题需要解决:
- 对于在此处引用但是在其它源码中定义的变量和函数,怎么找到他们?
- 怎么为每个 ELF 文件中的变量和函数生成独特且不彼此覆盖的内存位置?
解决方案分别可以用关键词概括:符号表和重定位。