函数调用
在大多数支持块结构的程序设计语言都支持函数或者子程序(函数和子程序的区别在于函数有返回值而子程序没有,在这里我们不区分这两个概念)。在进行函数调用和从函数返回时通常由一个被称为控制栈的运行时刻栈进行管理。每一个活跃的函数在控制栈中都会有一个相对应的活动记录,有时也称为栈帧。活动记录存储着函数调用时传递的参数信息和从函数返回时返回值与控制跳转的信息。
函数的活动记录需要包括下面的信息
- 控制链(control link):指向控制栈中前一个活动记录的指针;
- 访问链(access link):指向源程序中最近的外层块对应的活动记录,用于维护静态作用域(本文中不讨论);
- 返回地址:函数调用结束后被执行的第一条指令地址(本文讨论中将忽略);
- 返回结果地址:存放函数返回值位置的地址;
- 实际参数:函数调用时传递的参数值;
- 局部变量:函数体中声明的局部变量;
- 临时存储区:在函数执行过程中产生的临时结果。
不同语言的不同实现中对上述信息的存放顺序和存放方式可能不一样。在这里我们按照上面说明的顺序来进行讨论。
每当一个函数被调用时,就创建一个相对应的活动记录,并压入控制栈中;而当从一个函数调用结束时,在控制栈中与该函数对应的活动记录(即栈顶的活动记录)就会被弹出控制栈。每个函数所对应的活动记录的大小是不一致的,而堆栈寄存器每次记录的都只是栈顶的活动记录,所以在每个活动记录中需要一个控制链来记录前一个活动记录。
下面我们通过一个例子来简单演示一下函数的活动记录如何被压栈和弹出栈的。
int fact (int n)
{
if(n == 1) {
return 1;
}else{
return n * fac

本文介绍了函数调用的原理,涉及控制栈、活动记录等概念,并通过计算阶乘的例子展示了活动记录的压栈和弹出过程。接着探讨了尾递归的概念,指出尾递归在内存占用和效率上的优势,以及Scheme语言对尾递归的支持。最后讨论了如何将非尾递归转化为尾递归,以及这种转化的优缺点。
最低0.47元/天 解锁文章
691

被折叠的 条评论
为什么被折叠?



