运行时存储空间组织

  对于存储空间这章不是课程的重点,老师讲授的内容比较少。不过还是做一点儿整理,供以后复习参考使用。
  学习本章的重点在于了解不同情况下的内存分配策略,大致分为三种:静态分配、动态栈式分配、动态堆式分配。对于栈式只介绍一些很简单的,对于堆式没有介绍。其实这样也没有什么考点,权当扩充知识,了解在编译过程中内存的变化情况。
  学习存储分配,有几个基本的概念是要了解的(不一定要背下来,至少说出来要知道是啥吧~):
在这里插入图片描述
  这里面提到一个概念叫生存期,之前学高级语言的时候都讲变量都有作用域。那这两个有什么区别呢?生存期是有效时间长短,作用域是有效作用范围。有点儿像时间空间的感觉。生存期是时间,作用域是空间。
  无论是变量还是函数,都要有一个名字。这个名字指的不是变量本身的值,而是变量存储的地址。在访问这个变量或函数的时候,根据地址去取值。(这里我有一个疑惑:C语言中指针即是地址,如果上面的说法正确的话,对于int a;来说,a是变量名,是元素a存储的地址,a应该是一个指针。那和 int *a有什么区别呢?)我对这个问题自己思考了一下:在int a中这个a并没有从存储空间的角度考虑,只是代表了一个变量名字而已。只需要明白可以通过地址找到变量值这个思想。
  调用函数的时候要传递参数,在主调函数中的叫实参,被调函数中的叫形参。那 形参和实参间是如何实现传递的呢?主要有三种方式:传地址、传值、传名。
  顾名思义,传地址就是把参数地址送入被调函数,被调函数可根据此地址进行访问,即被调函数要拿出空间A存放该地址B,实际使用时访问A,取出B,按照B去拿C。(好绕!)。
  传值是把参数值送入被调函数,在被调函数中需要分配空间存储参数值,到时候取值即可。传名这个听的时候比较少,这个就是在传递参数的时候,形参和实参之间有一一对应的关系。前面两种当函数被调用的时候,内容就已经传递过去了。
  在传名中,直到调用该形参的时候才会去对应查找其实参。整体感觉有点像一个程序,使用时调用。所以也叫参数子程序。不过传名几乎不用,书上说只用于在ALGOL里面,了解一下就可以了。
  现在想一想,整个程序编译运行过程中。都要存储哪些内容?一是目标代码、二是数据信息、三是过程中调用的控制信息。其实还是比较好理解的,存储生成的目标代码,机器才能一步一步的执行指令进行调用。数据信息包括各种变量、过程中的控制信息主要是指各个层次之间的嵌套关系,谁调用谁应该心里有数。
  将以上三种内容落实到具体就是活动记录,下面介绍下活动记录的定义:把过程的一个活动所需要的信息组织成一块连续的存储单元,称为活动记录。常以表格的方式来表示,以Pascal语言为例:它的活动记录是下面这样的:
在这里插入图片描述
  活动记录大致可以分为三类:连接数据、形式单元、局部数据区。连接数据包括静态链、动态链(后面会详细介绍啥是动态链,静态链)、返回地址。局部数据区包括临时单元(中间代码生成的),内情向量(数组的首地址即大小范围等信息),局部向量(子函数内声明的变量),形式单元(调用函数时传递的形参)。
  下面介绍下三种存储分配策略:静态存储分配,栈式存储分配,堆式存储分配。其实理解这三者有一个很简单的方式(当然,简单意味着不是很严谨~)静态存储分配就是指程序被预先分配的空间,没有进行动态分配,编译时也不会附加任何空间。栈式存储分配是系统自动进行空间分配,最明显的例子就是递归算法。程序结束,地址空间释放。堆式存储分配的特点是由用户主动划分,比如C++语言中的new函数,同样可以进行归还,也就是C++中的delete。下面更为详细的介绍下三者:

静态存储分配:

  静态存储分配要满足如下三个条件:
在这里插入图片描述
  在静态存储分配里包括一些声明的变量空间的分配,在前面的文章中介绍过在做中间代码生成的时候,会引入大量的中间变量(虽然在最后的优化过程都会去除,但也要为其分配空间)。中间变量的大部分会被消除,如果为它们的每一个都分配空间显得有点儿浪费,所以选择了一种处理方式:
在这里插入图片描述
  有的时候,中间变量的生成之后,下一条语句就使用掉了,后面也不会再出现,对于这种情况也设定了一种机制:
在这里插入图片描述
  实例如下:
在这里插入图片描述

栈式存储分配

  关于栈式存储分配只介绍了简单的并且以C语言作为示范。栈式存储分配都在栈空间里完成,所以需要进行入栈管理。每个过程都以活动记录作为基本单位,过程发生活动记录入栈,过程结束出栈。
  那静态存储和栈式存储有什么区别呢(栈式相比于静态)?
在这里插入图片描述
  举一个抽象一点儿的例子:
在这里插入图片描述
  在这个例子中也不是说固定的子程序的活动记录向上增长,这个取决于栈顶,不断向下压栈。
  那栈式空间中C语言的活动记录是什么样的呢?
在这里插入图片描述
  上图的sp指的是stack pointer,即栈顶指针top。带个老字就是上一层的。

  除了简单的栈式实现,还有一种是嵌套过程语言的栈式实现。什么叫嵌套过程呢?就是指在一个函数中,调用了另一个函数(该函数有可能继续调用其他函数,但不能相互调用引发死循环,需要是合理的调用)。那这里有一个什么问题呢?就是变量使用的问题。在嵌套过程中,内层是可以使用外层的变量的。有一种极端情况,内外层变量名相同,这时会发生作用域的覆盖。内层调用的是内层,会覆盖掉外层。不考虑极端情况,在刚才的介绍中了解到每一层在栈中以活动记录为单位进行存储,那内层是如何访问到外层的活动记录呢?这部分就是主要解决这个问题的!
  解决的方式有两种:静态链或Display表。

  静态链

  静态链的思想就是每层活动记录保留上一层的sp,这样如果想访问外层变量就可以顺着sp向上查找。这种方式固定增加了一个sp空间,是静态的方式。其活动记录如下:
在这里插入图片描述
  解释下里面为啥有个静态链,还有个动态链。静态链就是我们刚才说的,用于进行变量查询。动态链就是之前栈式结构中的老SP,用来表示调用关系的。
  用一个例子,来说明下:
在这里插入图片描述
  想想静态链有什么问题吗?如果嵌套了特别多层,最内层想要调用最外层的变量需要一层一层向上查找。拿着必定会特别慢。有没有什么方式可以加快速度呢?这就是Display表!

  Display表

  Display表的思想就是反正一层一层查找的目的就是为了引用外层变量,那我直接把所有的外层变量放在本层的活动记录里不就可以嘛。这样免得去其他层找。变量的个数不固定,导致表的空间大小不固定,所以也是动态分配的办法,其活动记录结构如下:
在这里插入图片描述
  举个例子(当然使用标序号的方式,活动记录里都是序号了哈~):
在这里插入图片描述
  上图里面有一项叫做:全局display,这个指的是上一层的display段开始部分对应的标号。活动记录中display段的个数就反映了当前的嵌套层数。第一层的全局display默认为0。

  总结一下静态链和Display表的优缺点:

  静态链优点:空间固定,且占用空间小 缺点:访问速度太慢
  Display表优点:访问速度快 缺点:占用空间比较大

堆式动态分配

在这里插入图片描述
  在定义中使用“适当大”来形容空间分配的大小,但实际上堆式分配的大小是由用户指定。
  那如此多的空间,如何选出一个合适的呢?其实有三种策略:
在这里插入图片描述
  对于堆式分配,比较重要的一点就是内存空间的释放问题。如果不释放的话,内存就会被切分成小块儿,导致以后使用的时候没有足够大的分配。这也告诫使用者在malloc之后加free,在new后加delete。
  内存的分配始终是计算机科学的一个大的组成部分,如今在编译原理中又学了一遍,涨了不少见识。但还是冰山一角,日后多多加油!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值