通常,存储管理与具体目标机的体系结构是密切联系的。
目标程序运行过程中的存储空间为重点,故也称为运行时存储管理。
目标程序要实现一次运行,除了必要的可执行代码,还必须依托一个运行时环境。而许多与目标程序运行相关的工作都必须依赖这个环境,包括源程序中各种对象的存储分配及访问机制、参数传递、操作系统接口等。
1)逻辑地址和物理地址。在通用机平台上,编译器生成的目标程序通常不是独享整个系统资源的。其享有的存储空间是由操作系统依据特定的算法分配的。对于物理存储资源调配情况,编译器是完全无能为力的。目标程序如何实现对存储空间的访问是由编译器与操作系统甚至是目标机协作完成的
汇编存储分配的例子:从ASM文件到实际运行的过程可以分为以下两个阶段。
第一,汇编、链接阶段。Asm文件中并没有明确指出A、B符号的地址,只是说了A、B符号所需要的存储空间。经过汇编、链接之后,形成了可执行文件。一个可执行文件通常是以段的形式组织的,其中一部分用于存储数据,而另一部分用于存储可执行代码。在假设各段起始地址为0的情况下,汇编器会为各符号计算逻辑地址,即相对于段首地址的偏移。
第二,装入阶段。当一个可执行文件被执行时,操作系统会按照一定的调度顺序将可执行文件中的段读入物理存储空间中。通常,物理存储空间是共享的,操作系统会为各符号计算物理地址,即实际分配得到的存储地址,并修改相应的引用。
2)逻辑地址空间的组织形式。用户程序的逻辑地址空间的管理与组织是由编译器、操作系统、目标机共同完成的。通常,编译器会将逻辑地址空间划分为5个区域,即代码区,静态区,堆区,空闲区,栈区。
代码区主要用于存储程序代码,程序代码在程序运行过程中是保证不变的,对于拥有可写rom的目标机结构来说,代码区一般是存放在ROM中,这样既保证了代码的安全,也节省了有限的RAM空间。
静态区主要用于描述用户程序的静态数据,包括全局变量,静态变量等,静态区允许用户程序通过地址直接进行寻址。
栈区,堆区主要用于解决程序运行过程中动态存储分配的问题。有些目标机可能只支持代码区,静态区,那么,编译器就不得不在静态区模拟某些动态分配,回收的过程,以满足相应的需求,这种分配策略仍然是静态的,是在编译过程中由编译器完成的。
存储布局:
1)数据对象的存储布局受目标机寻址约束的影响很大,合理的存储布局对提高程序的执行效率是非常有效的。
所谓字长,就是CPU一次读,写数据的最大长度。在现代计算机体系结构中,为了便于设计,CPU通常是按整字访问数据的,即地址必须能整除机器字长。
在现代编译器设计中,人们提出了一种更有效的存储布局方案,就是打破声明次序的先后原则,按照数据对象的占用空间的实际大小顺序,由小至大(或反向)地依次分配。这样,就能有效地避免留白过多的问题。
编译器存储分配的基本单位是什么,几乎所有的编译器都是以过程或函数为单位来分配存储区的。
实际上,编译器只需将一个过程或函数内所有的局部变量都看做某一虚拟记录类型的字段列表。可以得到若干以过程、函数为单位的逻辑数据块以及若干全局变量的数据块,这就是存储分配的对象。
栈式存储分配:将过程或函数的局部数据对象分配在栈空间中,申请与回收存储空间付出代价较小,便于实现。
堆式存储分配:堆的管理离不开操作系统的协助,一般而言,操作系统会提供一个接口,用于申请与释放堆的空间。编译器同样生成响应的语句调用这个接口,实现空间的申请与释放。
有些语言将申请与释放堆空间的接口显示的暴漏给程序员,相应的责任由程序员来承担,典型的例子就是C,Pascal。然而有些语言仅开放申请接口,而释放空间的琐事就由垃圾回收机制来完成,以减轻程序员维护堆的负担,如java,c#。
栈式存储分配基础:
栈式存储分配的基本思想就是每当进入到一个过程时,就在栈顶为该过程分配所需要的数据空间,将那块与过程有关的数据空间称为活动记录AR。AR的空间一般由以下几个部分组成,过程局部变量的空间,实参列表,返回值空间,返回地址,机器状态等,
1)实参:从经典的编译技术的观点来看,实参是由过程调用压入控制栈。delphi将第一,二个参数通过ecx,edx传递,其他参数依然压入控制站,这就是fastcall调用约定,是继stdcall, cdecl之后另一种经典的额调用约定。
2)返回值。返回值是由函数执行完毕后,返回给调用的值。不过,一个函数最多只允许一个返回值,因此借助于寄存器传递返回值相对容易的多,在现代编译设计张,通过eax传递返回值几乎是公认的。
3)保存的机器状态。用于保存当前过程调用之前的机器状态信息,包括返回地址,通用寄存器的值等。
4)局部数据与临时变量。
5)控制链
6)访问链。
i386栈式存储分配:
在i386体系结构中,系统栈是由CPU直接管理和维护的,它主要涉及两个寄存器。ss, esp.
1)系统栈区域的初始状态。 编译器初始化部分区域。
2)系统栈的初始方向。
3)出栈元素并不清除。
栈式存储分配:
1)可变参数: 从传参的角度来说,可变参数的传递与普通参数的传递并不存在明显的差异,可变参数的关键问题在于函数本身是无法得知实参存储区域大小的,这个信息只能由调用点计算确定。在这种情况下,实参存储区的清理工作就只能由调用点来完成。在处理可变参数时,C编译器只会遵守_cdecl调用约定。
2)所谓存储优化就是通过优化算法以改善目标程序在执行过程中对存储资源的耗费。