值 之二 存储

存储


这一节介绍程序世界中的值在机器中是如何存储的。主要介绍程序变量的时空特性几种存储模型以及悬挂引用的相关内容。

 

1,程序变量的时空特性

   引用和指针

   由于变量的名值分离,当程序操作变量时,首先寻址再取内容。这是两种操作——取地址(引用)和取值(递引用)。

   指针本身也是程序对象,它也有地址,于是C语言允许多级指针的机制。其他语言只允许一级指针,除非指向复杂的数据结构内嵌的指针。

  所谓寻址即要给出编译(或解释)时程序变量名与地址的对照表。每当给程序对象分配存储时就在表中填入,这样就创建了引用(reference)。由于程序对象名字编译后不存在,每次操纵用的是地址,这个在对照表中有的地址即为该对象的别名。早期语言认为这是属于‘内部’的事,外叫‘变量’,内叫‘地址码’。而C++语言把这个占据存储的存储对象也上升为程序对象,叫做引用,使用户直接操纵地址。

 引用和指针的关系如下图所示:

   一般来说,通过指针变量引用某变量的内容叫做递引用。如C语言中的* 和数组下标表达式。

   变量的时态

      除了空间名值分离导出指针、引用、递引用程序世界的概念之外,变量还有时间特性, 以它的状态刻划:

       (1). 分配/未分配/除分配

 为程序对象分配存储就创建了存储对象,程序对象就可以访问了 。如果在编译时做这个工作我们叫静态分配,程序中变量多数如此;如果在程序运行时刻分配存储,我们叫动态分配,程序中的指针(或访问)类型变量则按动态分配,一般由程序员显式指定(用new操作)。

            若程序对象生命了但是未分配存储对象即投入运行,此程序对象处于未分配状态。

    撤消程序对象即除去已分配的存储对象,我们叫除分配或除配(deallocate)。除分配可由程序员显式指定(delete操作),也可以约定由语言的执行系统或操作系统自动实现。自动将当前无用的程序对象除配,并收回待用称无用单元回收(Garbage collection)机制。动态(或无)类型语言一般有此机制,静态类型语言虽然也有动态分配部分,往往不设此机制(如C语言)。

      (2). 定义/未定义/失去定义

   分配的存储单元总是有残值的(上个程序运行后留下的),这些值无任何意义(与本程序无关)。我们说,变量未定义

   如果通过赋值或赋初值,就是定义变量。

          如果该变量执行超出了我们指定它有意义的区域(例如,一个局部块外),只要它的存储没有撤消,它总是存在,但其内容()失去定义

2,组织存储对象的存储模型

     我们把寿命和程序一样长的变量称为全局变量;寿命和程序中的一个或者几个模块一样长的变量称为局部变量;寿命大于程序执行期的变量称为持久变量,如文件变量。

     在程序执行期间,一个存储单元可多次分配给局部变量,甚至同一存储对象可由多个程序对象共享。如C语言中的联合体。

     静态存储

      在程序运行之前分配的程序对象都叫做静态存储对象。一般是在编译时分配。近代语言有在装入内存后,运行前分配。之所以称为静态,是因为它们在分配之后整个程序执行期间不再改动。静态分配常常初始化。所有语言的全局变量多是静态对象。

       静态存储对象有以下优缺点:

   (1),虽然它易于差错,但是它不能支持递归,因为多次递归调用相关的动态参数无法分配。

(2),被迫使用易引起副作用的全局变量。

(3),占据的存储不到程序执行完毕不是放。有些大程序只有很多短寿命的变量应用,如果用静态存储就浪费了大量存储。即使有无用单元回收机制不到执行完毕也不回收它。

    需要注意的是,静态分配的不一定是静态变量,程序中的局部变量(如C中的auto变量)是静态分配的,运行时存储的地址可变。

   动态堆栈帧存储

     运行时堆栈是在内存中开辟一个空间,如同堆。首先将程序代码和全局变量、静态变量装入。当执行控制开始时,首先执行这一块,当执行到调用时,按编译时生成的嵌套关系,找出被调用的块,将该块的数据作为新的一帧,记下静态链,压入运行时对战后记下动态链(连接执行顺序)。由于编译时静态链的地址无法确定,静态链要根据祖先块分配存储对象去找,有时要用到局部变量和参数。动态链指出当前块的动态执行的父辈,以便在块出口时弹出栈内的执行指令。

     每个堆栈帧的组织如左图所示。

      


    动态堆存储

 动态堆栈存储虽然解决了碎片问题,但是存储对象寿命只能和堆栈帧同时生死,限制太严。而且在每一个块的进口处并不知道存储对象的大小和个数,以及要求存储对象比创建它的块的寿命长。所以堆存储有时是必不可少的。我们再详细讨论堆存储。

许多语言允许程序员显式分配动态存储,分配语句可以出现在程序中的任何地方,办法是调用操作系统ALLOC函数。ALLOC将存储对象分配在堆中并返回对该对象的引用。如前所述堆分配有时在连接的较小的空间上,算法比较复杂。所以要建立一处自由表登录已死存储对象的空间,并查清相邻的碎片予以合并。这些算法还必须高效,否则影响性能。故往往是折衷, 追求快速牺牲一些过小碎片(这又是潜在危险,当指针错误地指向这些无用小碎片时,后果极难预料)

      C语言的动态分配/除配

           分配:malloc(sizeof T)   ;  malloc (N)   ; calloc(N,sizeof(basetype)) ;

   除配:free (point);   //point必须指向已分配过的堆对象。该对象经此函数后即可再分配。

       死堆对象叫无用单元或垃圾。堆对象死亡是当它引用的对象已失去定义(即出了创建该对象的块),我们称这种死亡是自然死亡。强行用(或类似)操作系统KILL命令也能显式使之死亡。自然死亡一般程序员和运算支持系统都是察觉不到的, 不知不觉在一个未知的地方留下垃圾。程序员有义务删除对不再需要的对象的引用。

3,悬挂引用

指针指向一个已死或无定义的对象,就称为悬挂引用(Dangling References),或悬挂指针。

        悬挂引用大部分时候产生于堆式管理,如果除配时未将指向除配块的指针置空,就产生了悬挂指针;对于堆栈式管理,如果外块的指针指向内块的存储对象时也会产生悬挂指针,因为当内块执行完毕后除配,外块的指针依然活着可用,它就指向了无意义的存储单元了。

        不同的程序设计语言有不同的悬挂引用处理措施:pascal语言把指针限制为堆对象并且不用dispose、不提供地址运算、操作数组时不能按指针寻址;C语言对指针使用完全没有限制,悬挂指针的处理交给了程序员去处理;algo引用局部变量不赋给比它声明长的变量;函数式程序设计语言中的把函数作为第一类值是悬挂指针尤其严重,所以ML语言把所有变量作为堆变量存储,并且不提供强行KILL命令。

总体来说避免悬挂指针有如下思路:1)在对指针实施运算的地方,检查指针变量是否已经赋值,即指针变量是否已指向合法位置;2)在对指针变量赋值的地方,检查指针变量是否先前已指向其他合法位置,避免悬挂引用;3)在对指针变量赋值时,记录其“元素”的个数,以便能在其后的操作中检查是否越界;4)在对指针变量赋值的地方,记录引用对象已被哪些指针指向,为此引用对象除分配时,提出编译警告,告诉程序员哪些指针已经悬挂。

 

 

本节顺上节内容对值在内存中的存储做了简要介绍,下一节介绍一下值和机器中的存储的对应关系——束定(也叫绑定)。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值