编译原理

编译程序

从功能上看,一个编译程序就是一个语言翻译程序。语言翻译程序把一种语言(源语言)书写的程序翻译成另一种语言(目标语言)的等价程序。典型的高级语言程序的处理过程为:将预处理的源程序进行预处理得到源程序;将源程序进行编译,获得目标汇编语言程序;将目标汇编语言程序进行汇编,得到可再装配的机器代码;将可再装配的机器代码进行装配/连接编译程序,加上可再装配目标文件,得到绝对机器代码。

编译程序完成从源程序到目标程序的翻译工作,按过程可以分为:词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成6个阶段。这只是典型的编译过程,有的编译程序没有代码优化等过程。

运行时存储组织

通常,在有操作系统的情况下,目标程序将在自己的逻辑地址空间内存储和运行。编译程序所产生的目标程序的大小通常是确定的,一般存放在指定的专用存储区域,即代码区。相应的,目标程序运行过程中需要创建或访问的数据对象将存放在数据区。

程序运行时存储空间的布局

一般来说,程序运行时的存储空间从逻辑上可分为“代码区”和“数据区”,但为了方便存储组织和管理,往往需要将存储空间划分为更多的逻辑区域。具体的划分方法会依赖于目标机体系结构,但一般情况下至少含有保留地址区、代码区、静态数据区及动态数据区等逻辑区域。

  • 保留地址去。专门为目标机体系结构和操作系统保留的内存地址区。通常,该区域不允许普通的用户程序存取,只允许操作系统的某些特权操作进行读写。
  • 代码区。静态存放编译程序产生的目标代码。
  • 静态数据区。存放全局数据,是普通程序可读可写的区域。存放程序中用到的所有常量和数据对象,以及各类全局变量和静态变量所对应的数据对象。
  • 动态数据区。运行时动态变化的堆区和栈区。
  • 共享库和分别编译模块区。静态存放共享库模块和分别编译模块的代码和全局数据。

存储分配策略

数据区分为静态数据区和动态数据区(堆区、栈区),分别对应了3种规则:静态存储分配、栈式存储分配和堆式存储分配。

  • 静态存储分配,即在编译期间为数据对象分配存储空间。要求在编译期间就可以确定数据对象的大小,同时还可以确定数据对象的数目。如可全程访问的全局变量、静态变量、程序中的常量以及class的虚函数表等。
  • 栈式存储分配,将数据对象的运行时存储按照栈的方式来管理。栈式存储分配是动态的,也就说必须是运行的时候才能确定数据对象的分配结果。运行时每当进入一个过程/函数,就在栈顶为该过程/函数分配存放活动记录的数据空间,当一个过程/函数工作完毕返回时,它在栈顶的活动记录数据空间也随即释放。在编译过程中,过程、函数以及嵌套程序块的活动记录大小(最小值)应该是可以确定的,这是进行栈式存储分配的必要条件,如果不满足应该使用堆式存储管理。
  • 堆式存储分配,当数据对象的生产期与创建它的过程/函数的执行期无关时,例如某些数据对象可能在该过程/函数结束之后仍然长期存在,就不适合使用栈式存储分配。堆式存储分配和释放数据对象的操作是应用程序通过向操作系统提出申请实现的,因此要占用相当的时间。堆式存储空间的释放分为:不释放、显示释放(如malloc和free、new和delete)、隐式释放(垃圾回收)。

面向对象语言存储分配策略

面向对象语言中,与存储组织关系密切的概念是类和对象。类扮演的角色是程序的静态定义,对象扮演的角色是程序运行时的动态结构。

  • 类是一组运行时对象的共同性质的静态描述,包含变量和方法。存储在静态存储区。
  • 对象时类的一个实例,在堆区为对象申请空间并创建对象,同时在栈区保存引用对象的存储单元。在堆区的对象包含局部变量,但并不包含方法,而是有一个指向该方法的引用。