将抽象定义成一种过程
名字 映射到复杂的程序片段
从用途与功能角度考虑
而非具体实现
控制抽象:执行良好定义的操作
数据抽象:表示数据
子程序代表调用方执行操作
参数化
传递参数:传递信息,提供数据
实参在调用时映射到形式参数
返回值的函数
不返回:过程
大多数要求函数在使用前已有声明
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++隐式实例化规则比 重载子程序 严格
编译器不对子程序实参作类型强制
协程
需要闭包
继续:常量,创建后不改变
协程:每次运行时变化:跳进时位置为上次退出时位置
一组协程:同时存在的上下文,但每个时刻只有一个在执行
用于离散时间的模拟
要求顺序的完整性检查,替代深层循环
控制权转移
线程比协程好用
协程是并发的,不能共享栈
如果栈帧在堆中分配,可以避免溢出与内部碎片问题
但会增加子程序调用开销
仙人掌栈
表示协程或进程:上下文块
协程转移
事件
程序外部发生
时间不可预测
需要程序响应
等待输入:同步,阻断式
回调函数
处理程序:特定时间调用
异步设备活动
中断机制
切换栈到回调函数
信息缓冲区
基于线程处理程序
事件:单独控制进程