第9章运行时的存储组织.ppt
《第9章运行时的存储组织.ppt》由会员分享,可在线阅读,更多相关《第9章运行时的存储组织.ppt(126页珍藏版)》请在三一文库上搜索。
1、第9章 运行时的存储组织,School of Computer Science & Technology Harbin Institute of Technology,重点:符号表的内容、组织,过程调用实现, 静态存储分配、动态存储分配的基本方法。 难点:参数传递,过程说明语句代码结构, 过程调用语句的代码结构, 过程调用语句的语法制导定义, 栈式存储分配。,2019/4/17,2,第9章 运行时的存储组织,9.1 与存储组织有关的源语言概念与特征 9.2 存储组织 9.3 静态存储分配 9.4 栈式存储分配 9.5 栈中非局部数据的访问 9.6 堆管理 9.7 本章小结,2019/4/17,
2、3,9.1 与存储组织有关的源语言概念与特征,编译程序必须准确地实现包含在源程序中的各种抽象概念,如名字、作用域、数据类型、操作符、过程、参数和控制流结构等,这些概念反映了源语言所具有的一些特征,对它们的支持往往会影响运行时的存储组织和分配策略 给定一个源程序,编译程序必须根据源语言的特征(规定)为源程序中的许多问题做出决策,包括何时、怎样为名字分配内存地址。 静态策略:在编译时即可做出决定的策略 动态策略:直到程序执行时才能做出决定的策略,2019/4/17,4,9.1.1 名字及其绑定,“名字”、“变量”和“标识符” 的区别与联系 名字和变量分别表示编译时的名字和运行时该名字所代表的内存位
3、置。 标识符则是一个字符串,用于指示数据对象、过程、类或对象的入口。 所有标识符都是名字,但并不是所有的名字都是标识符,名字还可以是表达式。例如,名字x.y可能表示x所代表结构的域y。 同一标识符可以被声明多次,但每个声明都引入一个新的变量。即使每个标识符只被声明一次,局部于某个递归过程的标识符在不同的运行时刻也将指向不同的内存位置。,2019/4/17,5,名字的绑定,从名字到值的两步映射 环境把名字映射到左值,而状态把左值映射到右值 赋值改变状态,但不改变环境。 如果环境将名字x映射到存储单元s,我们就说x被绑定到s,名字,内存位置 (变量),状态,值,环境,2019/4/17,6,9.1
4、.2 声明的作用域,x的声明的作用域是程序中的这样一段区域,在该区域中,x的引用均指向x的这一声明。对于某种程序设计语言,如果只通过考察其程序就可以确定某个声明的作用域,则称该语言使用静态作用域;否则称该语言使用动态作用域。 C+、Java和C#等还提供了对作用域的显式控制,其方法是使用public、private和protected这样的关键字。 声明的作用域是通过符号表进行管理的,详见8.4节的讨论。,2019/4/17,7,1. 静态作用域,在具有程序块结构的语言中,变量声明的静态作用域规则如下 : 如果名字x的声明D属于程序块B,则D的作用域是B的所有语句,只有满足如下条件的程序块B除
5、外:B嵌套在B中(可以是任意的嵌套深度),且B中具有同一名字x的一个新的声明。 令B1, B2, , Bk是包围x的本次引用的所有程序块,Bk-1是Bk的直接外层程序块,Bk-2是Bk-1的直接外层程序块,如此类推。找到使x的某个声明属于Bi的最大i,则x的本次引用指向Bi中的这个声明。换句话说,x的本次引用处在Bi中的这个声明的作用域中。,2019/4/17,8,1. 静态作用域,表9.1 图8.10所示程序中声明的作用域,2019/4/17,9,2. 显式访问控制,类和结构为其成员引入了一种新的作用域 如果p是某个带有域(成员)x的类的对象,则p.x中x的引用将指向该类定义中的域x。与程序
6、块结构类似的是,类D中成员x的声明的作用域将会扩展到D的任何子类D,除非D中具有同一名字x的一个局部声明。,2019/4/17,10,2. 显式访问控制,通过使用像public、private和protected这样的关键字,C+或Java类的面向对象语言提供了一种对超类中成员名字的显式访问控制。这些关键字通过限制访问来支持封装。因此,私有名的作用域只包含与该类及其友类相关联的方法声明和定义,保护名只对其子类是可访问的,而公用名从类的外部也是可以访问的。,2019/4/17,11,3. 动态作用域,动态作用域规则相对于时间而静态作用域规则相对于空间 静态作用域规则要求我们找出某个引用所指向的声
7、明,条件是该声明处在包围该引用的“空间上最近的”单元(程序块)中。 动态作用域也是要求我们找出某个引用所指向的声明,但条件是该声明处在包围该引用的“时间上最近的”单元(过程活动)中。,2019/4/17,12,3. 动态作用域,“动态作用域”通常是指下面的策略 名字x的引用指向带有x声明的最近被调用的过程中x的这个声明。 例如,过程被当做参数进行传递时,2019/4/17,13,9.1.3 过程及其活动,将“过程、函数和方法”统称为“过程” 过程定义是一个声明,它的最简单形式是把一个标识符和一个语句联系起来。该标识符是过程名,而这个语句是过程体。 当过程名出现在可执行语句中时,称相应的过程在该
8、点被调用。过程调用就是执行被调用过程的过程体。注意,过程调用也可以出现在表达式中。,2019/4/17,14,9.1.3 过程及其活动,出现在过程定义中的某些标识符具有特殊的意义,称为该过程的形式参数,简称为形参。调用过程时,表达式作为实在参数(或实参)传递给被调用的过程,以替换出现在过程体中的对应形式参数。9.1.4节将讨论实参和形参的结合方法。 过程体的每次执行叫做该过程的一个活动。过程p的一个活动的生存期是从过程体执行的第一步到最后一步,包括执行被p调用的过程的时间,以及再由这样的过程调用其它过程所花的时间,等等。,2019/4/17,15,9.1.3 过程及其活动,如果a和b是过程的活
9、动,那么它们的生存期或者不交迭,或者嵌套。也就是说,如果在a结束之前b就开始了,那么b必须在a结束之前结束。 如果同一个过程的一次新的活动可以在前一次活动结束前开始,则称这样的过程是递归的。递归过程p也可以间接地调用自己。 如果某个过程是递归的,则在某一时刻可能有它的几个活动同时活跃,这时必须合理组织这些同时活跃着的活动的内存空间。,2019/4/17,16,9.1.4 参数传递方式,当一个过程调用另一个过程时,它们之间交换信息的方法通常是通过非局部名字和被调用过程的参数来实现的。 传值 传地址 传值结果 传名 其主要区别在于实参所代表的究竟是左值、右值还是实参的正文本身,2019/4/17,
10、17,1. 传值,计算实参并将其右值传递给被调用过程 传值方式可以如下实现: 被调用过程为每个形参开辟一个称为形式单元的存储单元,用于存放相应实参的值。 调用过程计算实参,并把右值放入相应的形式单元中。,调用者,被调用者直接使用,A,实际参数A,形式参数X,A的值,单向传值,2019/4/17,18,2. 传地址,调用过程将实参的地址传递给被调用过程 传地址方式可以如下实现: 如果实参是一个具有左值的名字或表达式,则传递该左值本身 如果实参是a+b或2这样的没有左值的表达式,则调用过程首先计算实参的值并将其存入一个新的存储单元,然后将这个新单元的地址传递给被调用过程,调用者,被调用者间址访问,
11、A,实际参数A,形式参数X,A的地址,传地址,2019/4/17,19,3. 传值结果,传值结果就是将传值和传地址这两种方式结合起来 传值结果方式可以如下实现: 实参的右值和左值同时传给被调用过程。 在被调用过程中,像传值方式那样使用实参的右值。 当控制返回调用过程时,根据传递来的实参的左值,将形参当前的值复制到实参存储单元。,调用者,被调用者,A,实际参数A,形式参数X,A的值,传值/传地址,A的地址,2019/4/17,20,4. 传名,用实参表达式对形参进行文字替换。 传名方式可以如下实现: 在调用过程中设置计算实参左值或右值的形实转换子程序。 被调用过程为每个形参开辟一个存储单元,用于
12、存放该实参的形实转换子程序的入口地址。被调过程执行时,每当要向形参赋值或取该形参的值时,就调用相应于该形参的形实转换子程序,以获得相应的实参地址或值。注意,形实转换子程序的运行环境是调用程序。形实转换子程序又称为换名子程序thunk。,2019/4/17,21,例,procedure swap(var x, y: integer); var temp: integer; begin 调用swap(i, ai) temp := x; temp := i; x := y; i := ai; y := temp ai := temp end,2019/4/17,22,子程序 P(X,Y,Z); Y:
13、=Y+1; Z:=Z+X,传值: 2,传地址: X=T=5,Y=Z=A=2 A:=A+1=2+1 A:=A+T=3+5 8,传名 A:=A+1=3 A:=A+A+B=3+3+3 9,主程序 A:=2; B:=3; P(A+B, A, A); print A,临时单元: T:A+B=5,2019/4/17,23,编译程序组织存储空间时必须考虑的问题,过程能否递归? 当控制从过程的活动返回时,局部变量的值是否要保留? 过程能否访问非局部变量? 过程调用的参数传递方式? 过程能否作为参数被传递? 过程能否作为结果值传递? 存储块能否在程序控制下动态地分配? 存储块是否必须显式地释放?,2019/4/
14、17,24,9.2 存储组织,9.2.1 运行时内存的划分 9.2.2 活动记录 9.2.3 局部数据的组织 9.2.4 全局存储分配策略,2019/4/17,25,9.2.1 运行时内存的划分,目标代码,静态数据,栈,堆,空闲 空间,2019/4/17,26,过程的每个活动所需要的信息用一块连续的存储区来管理,这块存储区叫做活动记录 假定语言的特点为:允许过程递归调用、允许过程含有可变数组,过程定义允许/不允许嵌套。 采用栈式存储分配机制 活动记录:运行时,每当进入一个过程就有一个相应的活动记录压入栈顶。活动记录一般含有连接数据、形式单元、局部变量、局部数组的内情向量和临时工作单元等。,9.
15、2.2 活动记录,2019/4/17,27,对任何局部变量X的引用可表示为变址访问: dxSP dx:变量X相对于活动记录起点的地址,在编译时可确定。,参数个数,返回地址,形式单元,临时变量,数组内情向量,简单变量,SP 0,1,2,旧SP,TOP,每个过程的活动记录内容 非嵌套语言(如C),2019/4/17,28,连接数据 返回地址 动态链:指向调用该过程前的最新活动记录地址的指针。 静态链:指向静态直接外层最新活动记录地址的指针,用来访问非局部数据。,静态链,动态链,形式单元,临时变量,数组内情向量,局部变量,SP0,1,2,返回地址,TOP,每个过程的活动记录内容 嵌套语言(如Pasc
16、al),2019/4/17,29,形式单元:存放相应的实在参数的地址或值。 局部数据区:局部变量、内情向量、临时工作单元(如存放对表达式求值的结果)。,静态链,动态链,形式单元,临时变量,数组内情向量,局部变量,SP 0,1,2,返回地址,TOP,每个过程的活动记录内容,2019/4/17,30,9.2.3 局部数据的组织,字节是可编址内存的最小单位。 变量所需的存储空间可以根据其类型而静态确定。 一个过程所声明的局部变量,按这些变量声明时出现的次序,在局部数据域中依次分配空间。 局部数据的地址可以用相对于某个位置的地址来表示。 数据对象的存储安排还有对齐的问题。 整数必须放在内存中特定的位置
17、,如被2、4、8整除 的地址,2019/4/17,31,9.2.3 局部数据的组织,在SPARC/Solaris工作站上下面两个结构的size分别是24和16,为什么不一样? typedef struct _a typedef struct _b char c1; char c1; long i; char c2; char c2; long i; double f; double f; a; b;,2019/4/17,32,9.2.3 局部数据的组织,在SPARC/Solaris工作站上下面两个结构的size分别是24和16,为什么不一样? typedef struct _a typedef
18、 struct _b char c1; 0 char c1; 0 long i; 4 char c2; 1 char c2; 8 long i; 4 double f; 16 double f;8 a; b;,2019/4/17,33,9.2.3 局部数据的组织,在X86/Linux机器的结果和SPARC/Solaris工作站不一样,是20和16。 typedef struct _a typedef struct _b char c1; 0 char c1; 0 long i; 4 char c2; 1 char c2; 8 long i; 4 double f; 12 double f;8
19、a; b;,2019/4/17,34,静态存储分配策略(FORTRAN) 如果在编译时能确定数据空间的大小,则可采用静态分配方法:在编译时刻为每个数据项目确定出在运行时刻的存储空间中的位置。 动态存储分配策略(PASCAL) 如果在编译时不能确定运行时数据空间的大小,则必须采用动态分配方法。允许递归过程及动态申请、释放内存。 栈式动态存储分配 堆式动态存储分配,9.2.4 全局存储分配策略,2019/4/17,35,9.3 静态存储分配策略,如果在编译时就能确定一个程序在运行时所需要的存储空间的大小,则在编译时就能安排好目标程序运行时的全部数据空间,并能确定每个数据项的地址,存储空间的这种分配
20、方法称为静态存储分配。必须满足如下条件: 数据对象的长度和它在内存中的位置在编译时必须是已知的; 不允许过程的递归调用,因为一个过程的所有活动都是用同样的局部名字绑定的; 数据结构不能动态建立,因为没有运行时的存储分配机制。,2019/4/17,36,某分段式程序运行时的内存划分,2019/4/17,37,每个数据区有一个编号,地址分配时,在符号表中,对每个数据名登记其所属数据区编号及在该区中的相对位置。 考虑子程序段: SUBROUTINE SWAP(A,B) T=A A=B B=T RETURN END,寄存器保护区,返回地址,A,T,B,0,1,a,2019/4/17,38,静态存储分配
21、策略,顺序分配算法(基于调用图) 按照程序段出现的先后顺序逐段分配,程序段 区域 021 2236 3754 5571 7294 95104 共需要105个存储单元,程序段号/所需数据空间,能用更少的空间么?,2019/4/17,39,分层分配算法,允许程序段之间的覆盖(覆盖可能性分析),程序段 区域 6 09 5 022 4 016 3 2340 2 1731 1 4162 共需要63个存储单元,思考:如何设计分配算法?,7/80,2019/4/17,40,9.4 栈式存储分配策略,如果过程允许递归 某一时刻过程A可能已被自己调用了若干次,但只有最近一次处于执行状态。其余各次等待返回被中断的
22、那次调用 必须保存每次调用相应的数据区内容(活动记录) 引入一个运行栈 让过程的每次执行和过程的活动记录相对应。 每调用一次过程,就把该过程的活动记录压入栈中,返回时弹出。 假设寄存器top标记栈顶,局部名字x的相对地址为dx,则x的地址为top+dx 或 dxtop,2019/4/17,41,9.4.1 调用序列和返回序列,过程调用和过程返回都需要执行一些代码来管理活动记录栈,保存或恢复机器状态等 过程调用和返回是通过在目标代码中生成调用序列和返回序列来实现的 调用序列负责分配活动记录,并将相关信息填入活动记录中 返回序列负责恢复机器状态,使调用过程能够继续执行 调用序列和返回序列常常都分成
23、两部分,分处于调用过程和被调用过程中,2019/4/17,42,9.4.1 调用序列和返回序列,即使是同一种语言,过程调用序列、返回序列和活动记录中各域的排放次序,也会因实现而异 设计这些序列和活动记录的一些原则: 以活动记录中间的某个位置作为基地址; 长度能较早确定的域放在活动记录的中间; 一般把临时数据域放在局部数据域的后面,以便其长度的改变不会影响数据对象相对于中间那些域的位置; 用同样的代码来执行各个活动的状态保存和恢复; 把参数域和可能有的返回值域放在紧靠调用者活动记录的地方。,2019/4/17,43,9.4.1 调用序列和返回序列,调用者和被调用者之间的任务划分,2019/4/1
24、7,44,9.4.1 调用序列和返回序列,一种可能的调用序列: 调用者计算实参 调用者把返回地址和sp的旧值存入被调用者的活动记录中,然后将sp移过调用者的局部数据域和临时变量域,以及被调用者的参数域和状态域 被调用者保存寄存器值和其它机器状态信息 被调用者初始化其局部数据,并开始执行。,2019/4/17,45,9.4.1 调用序列和返回序列,一种可能的返回序列: 被调用者将返回值放入临近调用者的活动记录的地方。 利用状态域中的信息,被调用者恢复sp和其它寄存器,并且按调用者代码中的返回地址返回。 尽管sp的值减小了,调用者仍然可以将返回值复制到自己的活动记录中,并用它来计算一个表达式。,2
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 运行 存储 组织
链接地址:https://www.31doc.com/p-2609975.html