第8章:子程序和控制结构——《实践之路》笔记

时间:2022-09-08 18:00:29

将抽象定义成一种过程

名字 映射到复杂的程序片段

从用途与功能角度考虑

而非具体实现

 

控制抽象:执行良好定义的操作

数据抽象:表示数据

 

子程序代表调用方执行操作

 

参数化

传递参数:传递信息,提供数据

实参在调用时映射到形式参数

 

返回值的函数

不返回:过程

 

大多数要求函数在使用前已有声明

c没有

 

声明:编译器可以检查调用与函数签名是否一致

 

调用序列:调用前后,被调用前后

 

回顾栈布局

调用栈上分配空间问题

 

子程序被调用时,栈顶部分配一个新栈帧:活动记录

包含:实参、返回值、簿记信息(返回地址,保存的寄存器)、局部变量、临时值

 

在任意给定时刻,栈指针寄存器保存最后一个已用位置,或第一个未用地址

帧指针保存栈顶帧内的一个地址

帧中对象访问:相对帧指针位移寻址

 

对象大小编译时未知,放在帧顶部大小可变区域,地址与内情向量保存在某个固定区域

不存在未知大小对象:帧中对象相对栈指针偏移量静态可知,不用帧指针

参数大小编译时未知:参数放在其他参数下方大小可变区域,地址与内情向量放在相对帧指针的已知偏移量处

          或调用方只传递临时地址与内情向量,被调方复制实参到栈顶可变区域

 

允许嵌套与静态作用域的语言:

对象可能在外围子程序中

维护静态链,找到这些非局部非全局对象

栈帧包含一个 对词法上外围帧的引用——静态链(指针)

为子程序返回时恢复而保存的帧指针——动态链(指针)

 

一个活动的子程序,其外围子程序是活动的

只有通过外围子程序才能见到且调用内层子程序

 

嵌套定义的子程序,只能从外围子程序进入?函数名对其他函数不可见?

 

调用序列

维护子程序调用栈

调用前后代码,被调前后代码

 

进入子程序的过程完成的工作

传递参数

保存返回地址

修改程序计数器

修改栈指针与分配空间

保存子程序可能修改的寄存器值(包括帧指针)

修改帧指针,指向新帧栈

执行新帧上的对象初始化代码

 

离开时的工作

传递返回参数,返回值

局部变量终结代码

释放帧栈(恢复栈指针)

恢复保存的帧指针

 

所有工作中一部分只能由调用方完成(传递参数)

其他可以共同完成

调用方做工作

被调方做工作:可以节省空间,子程序不止一次被调用

 

保存恢复寄存器

理想模式:调用方正在使用,而被调方用于其他目的。则将之保存(寄存器集合的子集)

实际:调用方保存所有正在使用的寄存器  被调方保存所有将要修改的寄存器

寄存器分组:调用方保存有价值的一组  被调方改写没有价值的一组

 

静态链维护

一部分维护工作必须由调用方完成:调用方的词法外层

调用方计算被调方的静态链,作为隐式参数传递

具体分两种情况:

1.被调方直接嵌套在内层

被调方静态链引用调用方的栈帧。调用方传递帧指针

2.被调方在调用方外k层

调用方对静态链做k次间接引用,传递给被调方

 

 

典型调用序列

 

调用方

保存寄存器

计算参数值,移入栈或寄存器

计算静态链,作为隐式参数传递

执行子程序调用指令,跳入子程序,返回地址放在栈,寄存器中

 

被调方

分配帧

保存原有帧值,赋予新值

保存寄存器

 

子程序完成

 

被调方

返回值移入寄存器或栈

恢复寄存器

恢复fp,sp

跳回返回地址

 

调用方

保存返回值

恢复寄存器

 

优化:

没有局部变量

不需要帧栈,参数来自寄存器

 

区头向量

取代静态链的小数组

 

空间换时间:保存部分静态链的地址

 

寄存器窗口

 

内联展开

增加了代码量

保持语义(相对宏)

编译器优化

 

参数传递

传递值,引用,闭包

 

规则唯一:c

规则不唯一:python?

 

值调用,传递副本。得到值

 

引用调用:传递地址。形参为实参的新名字,别名。

传递的必须是左值

用临时变量保存

 

引用传递为了修改参数

值传递+返回值:形参赋值回实参

 

讨论值模型与引用模型只对使用值模型的语言有意义?!

 

变量的引用模型不一定要求所有对象通过地址访问

 

java内部类型值模型,自定义为引用模型

无论变量是值还是引用,都通过复制来传递

 

函数意图

修改实参

保持实参

 

间接访问的代价

复制对象的代价(大型值)

 

只读参数:同时获得引用参数的效率与值参数的安全性

 

c:const声明:加工时常量

const指针:保证不引用其他对象  或  引用的对象不被修改

 

c++

引用参数&

只引用特定的对象

引用作为返回值

 

闭包作为参数

闭包:子程序的引用,并带有子程序引用环境

代码地址+引用环境

 

c:指向程序的指针

 

面向对象:模仿闭包行为

 

特殊目的的参数:

 

相似数组:不同语言,数组维数与边界的约束时间不同:编译时,加工时,执行时

语言中,针对参数的规则  比 针对变量的规则 宽松

推迟到运行时确定形状的数组参数:相似数组参数,开放数组参数

 

默认参数

修改函数默认行为

缺省实参:使用默认值

 

命名参数:相对于位置参数:关键字参数

混合使用:先位置参数

 

 

可变个数参数表

对静态类型不安全

对动态类型安全???

类型安全的静态类型:要求可变长参数表类型相同

 

函数返回

不区分表达式与语句的语言:函数的值=函数体(本身是一个表达式)的值

显式return:副作用:函数停止

返回值类型限定

返回复合值,元组

 

 

 

泛型子程序与模板

需要在不同类型对象上使用同一操作

OS:队列:保存进程、存储描述符、文件缓冲区、设备控制块、等

队列数据结构的特征与数据项的特征无关

 

隐式参数多态性:参数类型无描述,但依然是类型安全的

类型检查推迟到运行时

 

显式多态的泛型机制

C++模板

用于创建容器

容器:数据抽象,保存一组对象。操作与对象类型无关

 

泛型模块(或类)需要泛型子程序,实现操作

 

泛型参数:

java c#只允许传递类型

ada c++允许传递常规类型的值?子程序或类?

 

泛型代码:对类型参数的最低特性要求

 

适当的限制条件:显示描述,或编译器推断

 

不同的实现方法

Ada c++静态机制:编译时 创建使用泛型代码的多个实例,每个实例独立代码副本

C++:安排独立的类型检查

优化:共享代码

 

java一个泛型所有实例运行时共享代码

标准基类object的实例

 

静态实现泛型与宏 有共性

泛型    宏

内联函数  宏

编译器理解的  事后添加的,预处理器展开的

类型检查

常规作用域规则

 

 

泛型参数的约束条件

泛型也是种抽象:接口提供所有信息

Ada,java:限制泛型参数,显示说明参数类型支持的操作

java:从父类继承的接口实现

 

c++:摒弃显示限制,提供参数检查

参数类型不支持操作:静态语义错误避免隐式使用泛型参数类型的操作

 

 

隐式实例化

泛型函数看做重载

c++隐式实例化规则比 重载子程序 严格

编译器不对子程序实参作类型强制

 

协程

需要闭包

继续:常量,创建后不改变

协程:每次运行时变化:跳进时位置为上次退出时位置

 

一组协程:同时存在的上下文,但每个时刻只有一个在执行

用于离散时间的模拟

要求顺序的完整性检查,替代深层循环

 

控制权转移

 

线程比协程好用

 

协程是并发的,不能共享栈

 

如果栈帧在堆中分配,可以避免溢出与内部碎片问题

但会增加子程序调用开销

 

仙人掌栈

 

表示协程或进程:上下文块

 

协程转移

 

 

事件

程序外部发生

时间不可预测

需要程序响应

 

等待输入:同步,阻断式

回调函数

处理程序:特定时间调用

 

异步设备活动

中断机制

切换栈到回调函数

 

信息缓冲区

 

 

基于线程处理程序

 

事件:单独控制进程