函数调用与递归

引言

    “递归,就是递归的调用自己,直到满足结束条件并返回。”这是大部分材料对递归的定义,从程序上看也符合一般的观感。但是,不了解函数的调用过程,对这句话的理解还是会模糊不清的。函数是怎么调用自己,又是如何返回的?下面是常用来说明递归调用的例子:

/******
*
*递归求n的阶乘n!
*
*/
int factorial( int n )
{
    if( n == 0 )
        return 1;
    else
        return n * factorial( n - 1 );
}

递归是程序语言的精髓概念之一,彻底的掌握显然不会那么容易。“世界是复杂的,解释世界的理论需简单,并带有丰富的变化,方能可行。”

函数调用

    第一句的递归定义中就含有“调用”的概念。程序从main函数开始一步一步的调用函数达到程序设计的目的。显然“函数调用”也是程序语言的精髓概念之一了,而且是递归概念的前导。

    调用过程涉及到计算机CPU内部的一些概念,最主要的有栈(stack)、栈帧(fram stack)、返回值(一般通过ax寄存器)、基址寄存器(bp)、栈寄存器(sp)、指令指针寄存器(ip)等。言简意赅的说,每个函数在运行过程中,在栈上都以栈帧的形式保存临时变量,也就是中间结果。如果它还调用其他函数,需要传递的参数也是通过栈帧来传递的(不同体系结构可能不一样)。每个栈帧有开始和结束,bp寄存器保存当前栈帧的起始位置(保持不变),sp指示栈顶的位置,随着函数运行会有出栈、入栈操作,sp的值会随之发生变化。ip始终指向下一个运行的汇编代码的虚拟地址。调用函数(caller)在调用被调函数(callee)之前,首先会把参数压栈,然后运行call <function>指令,CPU就会把当前过程的下一条指令的地址压栈(这一步是CPU自动执行的,汇编代码看不出栈的变化),以便调用返回时继续执行;还会保存标志位寄存器flag和段寄存器cs(视不同调用类型而定)。callee运行,首先会把caller栈帧的bp压栈(返回时需要恢复),然后将当前的sp赋值给bp,表明这时的bp是callee的栈帧起始地址;然后取出caller传进来的参数(在caller栈帧中,通过bp寄存器为参考点获取参数,(bp+8),(bp+12)...)。这里可能会有点困惑的是(bp+0),(bp+4)的内容是什么?上面说到进入callee后首先要做的就push %bp,此处的bp寄存器保存的是上一栈帧的起始位置。而进入callee必须保存上一过程的返回地址。这样就清楚了(bp+0)的值是上一个过程的栈帧值,(bp+4)是callee返回后caller执行的下一个指令地址。通过这样的函数调用规约,程序运行时的每次调用就能准确无误。如果有返回值,一般以ax寄存器传递返回值,那么在返回caller后,需保存ax寄存器的值。

递归

    前面抛出函数调用的砖,目的还是为了引出递归的玉。上述分析能否得出,被调函数不能是调用函数自身,也就是caller和callee不能是同一个函数?答案是,否!调用过程是通过栈来维护的,栈处于caller与callee的中立位置。逻辑上caller和callee是否同一函数,对栈来说并无区别,都必须将参数入栈,caller中下一条指令的地址入栈,call <function>,在caller中取出参数,执行判断或(和)运行,然后返回或者继续调用下一个过程。在求阶乘的例子中,函数factorial首先判断n是否为0,如果是则返回1,否则返回n*factorial(n-1),这个表达中有个子表达式就是调用factorial自身,然后与n相乘。那么,必须首先求出子表达的值,也就是调用factorial计算参数为n-1时的返回值。在这里可以预测,假设返回值是x,返回后首先必须计算n*x,并把它作为最终的factorial返回值。要得到x必须判断n-1是否为0,不为0还要继续调用,并且将调用的返回值与n-1相乘,一直递归这个过程,直到n减到0,最终返回。然后逐步执行上一个caller的相乘操作,并返回到上上一个caller……

总结

    函数调用过程中,并没有规定这个被调函数不能是自身,而递归恰恰是就是函数调用中的特例,caller和callee是同一个函数。因此,递归函数体必须包含调用截止的判断,否则无限制的递归调用必然导致栈溢出而使程序崩溃。形式上看,递归函数包含两个部分,一个是结束判断(terminating cases),一个是递归调用(recursive cases)。factorial函数的recursive case只有一个,属于线性递归。当有多个recursive case时就是树形递归(tree recursion)。但是递归过程的实质是一样的,其大体过程也相似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值