递归在C语言中的工作原理

递归在C语言中的工作原理,c,recursion,C,Recursion,我是C语言的新手,我正在阅读有关递归的书籍,但我完全搞不懂 我感到困惑的主要部分是,当达到退出条件时,事情是如何得到放松的。我想知道在递归过程中,值是如何从堆栈中推送和弹出的 还有谁能给我一个递归的诊断视图吗 谢谢…让我们假设一个函数: int MyFunc(int counter) { // check this functions counter value from the stack (most recent push) // if counter is 0, we'v

我是C语言的新手,我正在阅读有关递归的书籍,但我完全搞不懂

我感到困惑的主要部分是,当达到退出条件时,事情是如何得到放松的。我想知道在递归过程中,值是如何从堆栈中推送和弹出的

还有谁能给我一个递归的诊断视图吗


谢谢…

让我们假设一个函数:

int MyFunc(int counter) {
    // check this functions counter value from the stack (most recent push)

    // if counter is 0, we've reached the terminating condition, return it
    if(counter == 0) {
        return counter;
    }
    else {
        // terminating condition not reached, push (counter-1) onto stack and recurse
        int valueToPrint = MyFunc(counter - 1);

        // print out the value returned by the recursive call 
        printf("%d", valueToPrint);

        // return the value that was supplied to use 
        // (usually done via a register I think)
        return counter;
    }
}

int main() {
    // Push 9 onto the stack, we don't care about the return value...
    MyFunc(9);
}
输出为:012345678

第一次通过
MyFunc
,计数为9。它没有通过终止检查(它不是0),因此调用递归调用,使用
(计数器-1)
,8

这会重复,每次递减推送到堆栈上的值,直到
计数器==0
。在这一点上,终止子句激发,函数只返回计数器(0)的值,通常在寄存器中

下一次调用堆栈时,使用返回的值打印(0),然后返回调用堆栈时提供给堆栈的值(1)。这重复了:

下一次调用堆栈时,使用返回的值打印(1),然后返回调用时提供给堆栈的值(2)。等等,直到你到达栈顶

因此,如果用3调用了
MyFunc
,那么您将得到等价物(忽略堆栈中的返回地址等):


在C语言中,递归就像普通的函数调用一样

  • 调用函数时,参数、返回地址和帧指针(我忘记了顺序)被推送到堆栈上
  • 在被调用的函数中,首先在堆栈上“推”局部变量的空间
  • 若函数返回某些内容,则将其放入某个寄存器(取决于体系结构,AFAIK)
  • 撤消步骤2
  • 撤消步骤1

  • 因此,对于递归,步骤1和2执行几次,然后可能执行3次(可能只执行一次),最后执行4和5次(与1和2的次数相同)

    另一个答案是,一般来说你不知道。作为一种语言,C没有任何堆。编译器使用一个名为堆栈的内存位置来存储控制流信息,如堆栈帧、返回地址和寄存器,但C语言中没有禁止编译器将这些信息存储在其他地方的内容。对于实践方面,前面的答案是正确的。这就是C编译器今天的工作方式

    当达到退出条件时,事情是如何展开的? 首先,关于递归的几句话:a用于复杂任务,可以逐渐分解并简化为初始任务的简单实例,直到达到允许直接计算的形式(基本情况)。这是一个与政治密切相关的概念

    更具体地说,递归函数直接或间接地调用自身。在直接递归函数中,
    foo()。在间接递归中,函数
    foo()

    例子: 阶乘n,表示为n!,是从1到n的正整数的乘积。对于n>0,阶乘可以正式定义为:
    阶乘(0)=1,(基本情况)
    阶乘(n)=n*阶乘(n-1)。(递归调用

    递归出现在这个定义中,因为我们用阶乘(n-1)定义factorial(n)

    每个递归函数都应该有终止条件来结束递归。在本例中,当n=0时,递归停止。用C表示的上述函数为:

    这个例子是一个直接递归的例子

    这是如何实现的?在软件层面,它的实现与实现其他功能(过程)没有区别。一旦您了解到每个过程调用实例都不同于其他实例,递归函数调用本身的事实就不会有太大的区别

    每个活动过程都维护一个存储在堆栈上的。激活记录由参数、返回地址(调用方的)和局部变量组成

    激活记录在调用过程时出现,在过程终止并将结果返回给调用方后消失。因此,对于每个未终止的过程,存储包含该过程状态的激活记录。激活记录的数量,以及运行程序所需的堆栈空间量,取决于递归的深度

    还有谁能给我一个递归的诊断视图吗? 下图显示了阶乘(3)的激活记录:

    从图中可以看出,对阶乘的每次调用都会创建一个激活记录,直到达到基本情况为止,从那里开始,我们以乘积的形式累积结果



    这个问题已经得到了广泛的回答。请允许我用一种更具教育性的方法来补充一个答案

    你可以把函数递归看作是一堆泡泡,有两个不同的阶段:推进阶段和爆破阶段


    A) 推进阶段(或OP称之为“推进堆栈”) 0)启动气泡#0是主要功能。它被以下信息放大:

    • 局部变量
    • 对下一个气泡#1的调用(对递归函数的第一个调用) 函数,MYFUNC)
    1) 气泡#1在轮到它的时候会被以下信息炸毁:

    • 上一个气泡的参数#0
    • 局部变量(如有必要)
    • 回信地址
    • 使用返回值终止检查(例如:if(counter==0){return 1})
    • 呼唤下一个泡沫#2
    Call MyFunc(3) Stack: [3] Call MyFunc(2) Stack: [2,3] Call MyFunc(1) Stack: [1,2,3] Call MyFunc(0) Stack: [0,1,2,3] Termination fires (top of stack == 0), return top of stack(0). // Flow returns to: MyFunc(1) Stack: [1,2,3] Print returned value (0) return current top of stack (1) // Flow returns to: MyFunc(2) Stack: [2,3] Print returned value (1) return current top of stack (2) // Flow returns to: MyFunc(3) Stack: [3] Print returned value (2) return current top of stack (3) // and you're done...
    int fact(int n){
        if(n == 0){ 
            return 1;
        }
        return (n * fact(n-1));
    }
    
    /*Bubble #3 (MYFUNC recursive function): Parameters from Bubble #2,
    local variables, returning address, terminating check (NAIL),
    call (not used here, as terminating check is positive).*/