理解函数的调用过程 / pl0 语言
静态链和动态链
符号表的组织
pl0语言当中,符号表是全局的,动态变化的
增加符号
符号的属性
- 层
- 其实是对应的基地址阶数
- 相对偏移地址
- 和通常意义上的栈区内存分配异曲同工
删除符号
不做内存处理,直接退回符号栈栈顶指针
函数
函数是如何被调用的
函数当中包含函数的逻辑
,以及局部变量
深入理解计算机系统(3.7)------过程(函数的调用原理)
机器用栈来传递过程参数、存储返回信息
允许嵌套定义的函数如何访问外部变量
- 根据
lev_def
找到基地址 - 根据给定的
offset
进行栈偏移寻址,得到变量
pl0语言当中层差的作用
外层不能访问内层的变量
😂人不可貌相
⭐根本原因:被调用函数并不知道调用者是谁
The callee doesn’t know who calls it😂
但是在创建栈帧的时候,我们可以指定一个
比如
main / 0
var a1,b1;
p / 1
var a2,b2;
q / 2
var a3,b3;
r1 / 3
r2 / 3
r 的过程体 / 4
var c1,d1,e1,f1;
s / 1
var c2,d2,e2,f2;
t / 2
var c3,d3,e3,f3;
u / 3
u 的过程体 / 4
【main 函数体】
首先需要明确
lev = k 的函数必须在
lev >= k
的过程当中调用即任意内层和直接外层
比如 main
调用 p
因为main 虽然在 lev0,但是main 的过程体在lev1,而p的定义lev1(p的过程体在lev2)
当r 的过程体(lev = 4)调用p(lev = 1) 时
会产生如下的cal
指令
cal 3(lev_dif),offset(p)(base1)
当t 的过程体(lev = 3)调用p(lev = 1) 时
cal 2(lev_def),offset(p)(base1)
两个指令当中,p的新栈帧的静态链都会指向main
👇和base函数有关
base 函数
case cal: /* 调用子过程 */
// ^ 静态链,指向定义过程中的直接过程,用于访问变量
s[s_top] = base(cur_instr.lev_dif, s, stack_base);
// ^ 动态链,指向调用者的基地址
s[s_top + 1] = stack_base;
// ^ 存储PC
s[s_top + 2] = instr_ptr; /* 将当前指令指针入栈 */
// instr_base = base(i.lev_dif, s, instr_base);
stack_base = s_top; /* 改变基地址指针值为新过程的基地址 */
instr_ptr = (int)cur_instr.a; /* 跳转 */
break;
base 函数如下
int base(int lev_dif, double *s, int stack_base)
{
int b1;
b1 = stack_base;
while (lev_dif > 0)
{
b1 = s[b1];
lev_dif--;
}
return b1;
}
r 的过程体当中调用p 时,栈帧的分布是
main->p->q->r->p
调用p 的时候,会沿着r 栈帧的基地址开始,回溯 2 步(层差 = 2)
过程是
lev_dif = 3; b = r栈帧底部;r 栈帧底部指向了r的调用者 q 的帧底 lev_dif = 2; b = q栈帧底部;q 栈帧底部指向了q的调用者 p 的帧底 lev_dif = 1; b = p栈帧底部;p 栈帧底部指向了p的调用者 main 的帧底 lev_dif = 0; done;
同样的offset
,只是层差不同,不同父过程调用区别在哪里呢?
层差出现在 lod
,sto
,cal
这三个原生命令的执行过程当中
在 pl0语言当中,符号表是随时变化的
访问本栈帧的变量当然没有问题,但是如何访问外部变量呢?
其本质目的是内层(高层)函数想要访问外层变量时,需要找到合适的基地址,才能根据偏移量找到实际的栈地址,取出变量
层差就是需要沿着静态链回溯的阶数
所有变量都封装在当前层数的栈帧之上,需要得到栈帧基地址(%ebp)
⭐⭐⭐静态链的作用:指向定义该过程的直接外过程的最新活动记录的栈帧基地址
- 定义
- 最新活动记录
- 基地址
为什么在C 语言当中我们不常看到 层差
这个概念?
C 语言不允许函数嵌套定义,因此仅有局部变量($fp
偏移)和全局变量(.data
),并不存在需要变换基地址访问的情况
ref
本文来自博客园,作者:ZXYFrank,转载请注明原文链接:https://www.cnblogs.com/zxyfrank/p/13938853.html