第3章:名字 作用域 约束——《实践之路》笔记

一个对象拥有其语义价值的区域<其作用域

当一个变量将不再被使用,那它应该被理想的回收机制回收。但现实是我们仅当一个变量离开了其作用域,或变成不可访问,才考虑回收

然而,作用域规则有其优点:1.可以一次性考虑一组语义相关的变量,并能对其进行成批回收

相对于显式作用域规则隐式的自动垃圾回收将尝试提前回收作用域周期较长的变量。

但另一方面,我们需要更灵活的引用环境规则,来获得更丰富的语义表述。

 

 引言

语言设计的目标:高级特征——高抽象级别

机器无关性:不依赖于特定指令集

高级语言把与机器相关的工作推给了编译器与解释器

 

名字:有效标识符(词法分析,扫描器),用来表示变量 ,常量,操作,类型等。完成了一次抽象。

 

低级  高级

地址  指针

 

抽象是一个过程,将一个名字与一个程序片段相关联,封装成一个接口。

给程序片段取名的过程。

名不正则言不顺

 

子程序:控制抽象

类:数据抽象

 

抽象隐藏了细节,减少了概念的复杂性。

概念的打包

 

##在3-8章中用对象表示可以有名字的东西 

 

3.1约束时间:名字与事物的约束

 

约束时间:实现决策的期间。

##在各个阶段确定一部分语义消除不确定性

 

语言设计时:控制流程,内部数据结构,类型构造器等语言语义

语言实现时:类型精度,IO连接到操作系统,栈堆组织方式,异常处理

编写语言时:算法,数据结构,名字

编译时:高级结构映射机器代码静态定义数据内存中布局

连接时:标准子程序库或其他模块 连接

装入时:为对象选择机器地址虚拟地址翻译到物理地址计算映射

运行时:值与变量的约束。程序启动时,模块入口时,加工时(首次声明),子程序调用时,分程序进入时,语句执行

 

##术语:静态,动态

##编译器并没有尝试执行程序,所以在编译期间的获得的信息都是静态

##编译后,依然存在不确定性,这些不确定性只有在运行时才能得到信息,运行期间得到的信息都是动态

##编译器所做的努力就是尽可能的推断程序的信息,从而对运行时作出安排 

 

 

3.2对象与名字的生存期

:为对象分配释放存储空间的机制,

命名&约束

名字引用对象

关键事件:对象/名字 的 创建/引用/失活激活/撤销

 

生存期:创建——撤销

对象生存期与名字不重合:悬空引用,内存泄漏

作用域:进入(声明),退出,失活激活(执行进入嵌套作用域或退出作用域),结束(不再访问

 

生存期对应于三种主要的存储分配机制,用于管理 对象空间

静态对象:绝对地址,全局

对象:先进先出分配释放,对应于子程序调用与退出

对象:任意时间,要求更通用的存储管理算法

 

3.2.1.静态分配:

全局变量,语句翻译成的指令,子程序在调用间保持值局部变量常量字面量(小的保存在指令中,大的保存在存储区),

编译器生成的:表格

       支持运行时的例程:调试,动态类型检查,废料收集,异常处理

 

其中执行时不该改变的:指令,常量,运行时表格,分配受保护的只读区,写操作导致处理器中断

编译时常量:常量,常量调用内部函数&算术运算符,

加工时常量:依赖于程序具体运行,加工后不变

##不会同时出现对同一子程序的多个活动状态的调用——静态分配局部变量???

 

编译器负责保存加工时常量,局部变量

        子程序相关信息:参数值&返回值:寄存器或内存,

                临时量:寄存器,

                簿记信息:返回地址,动态链,寄存器内容,错误信息

 

3.2.2.栈

递归结构:局部变量不能静态分布——一个变量可以同时拥有多个实例个数

嵌套结构:容易分配栈空间

子程序实例:在栈上的帧中,包含:参数,返回值,局部变量,临时值,簿记信息

 

栈维护子程序的调用序列:调用者前后,被调用者前后代码

编译器不能确定帧栈地址,但(静态)确定对象在帧内的偏移(寄存器,帧指针)

局部变量,临时值,簿记信息:帧负偏移

参数,返回值:帧正偏移调用方帧内

 

当前活动子程序数目<<all子程序数目,栈中分配好于静态

栈指针:第一个未用位置

帧指针:帧中已知位置

 

3.2.3.堆

大小可变的对象:赋值与更新操作(为什么栈不维护大小可变的对象?效率?静态信息?

堆空间管理:速度与空间的权衡

 

内部碎片:供>求(原因:固定块大小,对象变小)

外部碎片:max供<求<all供(原因:切块)

 

自由表未使用空间的链接表

 

最先适配:

最佳适配:搜索开销 ,较少的外部碎片

供>>求时,两种算法都切块

合并相邻未分配块

 

维护单一自由表分配的代价与自由块数目,线性相关(搜索)

为不同大小的块维护不同的自由表,将堆划分存储池

划分:静态

   动态:伙伴系统:2的乘幂;斐波那契堆。及时合并

外部碎片问题:碎片数量随时间递增,max块递减

移动已分配块,堆的紧缩

 

废料收集

手动:识别对象的生存周期的结束。悬空引用,流失存储

自动:边际复杂性降低

 

 

3.3作用域规则

 

约束作用域约束起作用正文区域,大多为静态确定。纯粹根据正文规则,知道名字引用哪个对象

 

子程序入口引入作用域

建立局部对象约束

遮蔽全局对象

 

一个作用域:程序中,约束关系不会变化(至少不会撤销)的最大区域

常见作用域类型:模块,类,子程序,结构化控制流 的主体(块)

 

 加工:控制流初次进入作用域,各个声明激活。伴随着约束的建立

可能伴随着为局部对象分配栈空间赋初始值

Ada:执行错误检查,堆空间分配相关代码、传波异常、创建并发执行的作业

 

引用环境:执行中的给定点,{所有活动状态的约束的集合}

 对应于一系列作用域,检查并确定名字对应的约束

 

约束规则:对S的引用,与S的引用环境,两者的约束在何时完成

深约束:初次创建引用时确定

浅约束引用被使用时确定

 

 3.3.1静态作用域

 程序正文中包围的最近处的匹配

 

变量:默认声明隐式声明

 

 覆盖规则:局部变量一次执行后销毁,提升至整个执行期

 

 3.3.2嵌套子程序

 作用域嵌套

 

预定义对象:全局作用域外的一层

 

空洞:被同名遮蔽

修饰词作用域解析运算符:访问外层同名变量

 

帧指针寄存器:偏移寻址基点+偏移量访问局部变量

 

帧中维护静态链接:指向父帧词法外围,最近调用

运行时反引用(加上偏移量寻找外层变量

 

3.3.3声明的顺序

 声明下方可见:C,java

声明所在整个块可见:python

声明可能相互引用

 

C++,Java

免除了使用前必须声明的要求

类的成员在该类的所用方法中可见

 

Python:子程序局部变量:被赋值的变量

非局部变量只读的,除非显式导入:global

 

声明与定义:

语言要求使用名字前必须声明

递归类型,子程序

C:区分对象的声明与定义

声明:引进对象的名字,指明作用域,可能省略实现细节(静态?)

定义:描述对象细节,使编译器确定其实现

 

C:允许声明出现在任何语句上下文

 

Java:不允许重名

 

嵌套块中声明的变量不需要额外的空间分配与释放工作。与子程序局部变量类似

 

ML:重新声明

 

3.3.4模块

工作分配

信息隐藏

减少信息量,减轻思维负担

接口尽可能小,设计决策隐藏在模块中

减少名字冲突

保护数据抽象的完整性

减少错误扩散:在作用域中寻找

 

封装数据子程序

静态变量:子程序的记忆

单一子程序抽象

无法构造多个子程序构成的抽象:如多个子程序共享的变量

 

模块作为抽象

封装一组对象:内部可见,外部不可见(显式导出)

内部对象对外部也不可见

java:包

c++:命名空间

c:分别编译机制模拟模块行为

 

导入与导出

受限导出

变量:只读

类型:非透明???

 

模块的闭作用域:名字必须显示导入

开作用域

 

java ,python:选择性开作用域

模块A 导出foo

模块B 导入模块A   A.foo

   导入foo    foo

 

每个模块只能定义一个抽象

 

3.3.5模块类型与类

模块作为管理器

    类:模块中内容属于使用者

模块用于支持分别编译减少名字冲突

 

模块分为头部:用于编译,编译不依赖于体

 

面向对象

看做扩充了继承机制模块类型

 

模块类型或类的实例A,在A中包含所有内部变量单独副本??这样,执行A的操作时,所有变量可见。传参A,可引用A中变量

 

模块:功能划分

类:多实例抽象

 

3.3.6动态作用域

约束依赖于运行时的控制流,尤其是子程序调用顺序

最新遇到并且没有撤销的

运行前无法检测到引用环境相关的错误(至少类型崩溃错误)

容易实现

 

3.4作用域的实现

编译器:符号表字典):跟踪静态作用域的各个名字

名字——编译器已知信息

静态作用域规则增加了程序复杂性:一个名字,程序不同部分不同对象

 

静态作用域的变化:扩充基本符号表,跟踪可见性

任何东西都不会从符号表删除

保存,供调试器或运行时反馈机制使用

 

关联表:A表:名值对

实现动态作用域时像:声明——压入;作用于结束——弹出

中心引用表:避免线性搜索,显式维护映射

 

 

3.5别名,重载,多态

作用域中名字的含义

确定了作用域,去歧义,得到确切的映射关系

 

同一位置,不同名字同一对象别名

同一名字,不同位置,不同对象重载

 

别名的出现

指针

引用传参

使程序难以理解(编译器)

 

resrict 限定符 用于指针声明

指针所引用对象在当前作用域无别名,地址唯一

 

 3.5.2重载

 C++同一作用域,同一名字,指代不同子程序,只需函数签名不同

 

 3.5.3多态性

 强制,重载,多态

强制:编译器控制,为满足上下文条件隐式完成。存在开销,传参时也存在,修改参数

 

多态:子程序不加转换地接收多种类型参数

 

支持的类型满足某些共性

参数化多态性中,显式,隐式地以类型为参数

子程序多态性中,设计成作用于父类,子类也可使用

 

 显式:泛型

C++模板

 为每种类型创建一个不副本

 

继承:一份副本,在对象的表示中插入足够多的元数据???

 

隐式

运行时检查:一份代码,检查参数是否支持操作

编译时检查:多份代码,类型检查

 python 运行时检查

 

强制有隐含成本

 

 泛型需要传入类型type

 

重载:不同对象

泛型:同份代码

 

3.6引用环境的约束

作用域规则

确定了给定语句的引用环境

 

允许创建子程序引用的

何时将作用域规则应用于子程序?

创建引用时,约束

最终调用时,约束

 

子程序第一次被作为参数传递时做好环境约束!!!

 在子程序最终被调用时恢复环境

 

 3.6.1子程序闭包

 

为实现深约束,需要创建引用环境显式表示

子程序被调用时在其中运行,与子程序引用绑定在一起

闭包

 

 静态作用域语言,闭包捕捉闭包创建时的环境

调用时恢复环境

 

程序运行中,一个递归子程序中声明的对象可以存在多个实例

 

 

 

 

 

 

 

3.7宏扩展

 

posted on 2018-05-26 11:52  秦梦超  阅读(158)  评论(0编辑  收藏  举报

导航