Understanding how recursive functions work

后端 未结 18 797
野性不改
野性不改 2020-11-22 07:08

As the title explains I have a very fundamental programming question which I have just not been able to grok yet. Filtering out all of the (extremely clever) \"In order to

18条回答
  •  醉酒成梦
    2020-11-22 07:33

    You might be interested in Nisan and Schocken's implementation of functions. The linked pdf is part of a free online course. It describes the second part of a virtual machine implementation in which the student should write a virtual-machine-language-to-machine-language compiler. The function implementation they propose is capable of recursion because it is stack-based.

    To introduce you to the function implementation: Consider the following virtual machine code:

    enter image description here

    If Swift compiled to this virtual machine language, then the following block of Swift code:

    mult(a: 2, b: 3) - 4
    

    would compile down to

    push constant 2  // Line 1
    push constant 3  // Line 2
    call mult        // Line 3
    push constant 4  // Line 4
    sub              // Line 5
    

    The virtual machine language is designed around a global stack. push constant n pushes an integer onto this global stack.

    After executing lines 1 and 2, the stack looks like:

    256:  2  // Argument 0
    257:  3  // Argument 1
    

    256 and 257 are memory addresses.

    call mult pushes the return line number (3) onto the stack and allocates space for the function's local variables.

    256:  2  // argument 0
    257:  3  // argument 1
    258:  3  // return line number
    259:  0  // local 0
    

    ...and it goes-to the label function mult. The code inside mult is executed. As a result of executing that code we compute the product of 2 and 3, which is stored in the function's 0th local variable.

    256:  2  // argument 0
    257:  3  // argument 1
    258:  3  // return line number
    259:  6  // local 0
    

    Just before returning from mult, you will notice the line:

    push local 0  // push result
    

    We will push the product onto the stack.

    256:  2  // argument 0
    257:  3  // argument 1
    258:  3  // return line number
    259:  6  // local 0
    260:  6  // product
    

    When we return, the following happens:

    • Pop the last value on the stack to the memory address of the 0th argument (256 in this case). This happens to be the most convenient place to put it.
    • Discard everything on the stack up to the address of the 0th argument.
    • Go-to the return line number (3 in this case) and then advance.

    After returning we are ready to execute line 4, and our stack looks like this:

    256:  6  // product that we just returned
    

    Now we push 4 onto the stack.

    256:  6
    257:  4
    

    sub is a primitive function of the virtual machine language. It takes two arguments and returns its result in the usual address: that of the 0th argument.

    Now we have

    256:  2  // 6 - 4 = 2
    

    Now that you know how a function call works, it is relatively simple to understand how recursion works. No magic, just a stack.

    I have implemented your sumInts function in this virtual machine language:

    function sumInts 0     // `0` means it has no local variables.
      label IF
        push argument 0
        push argument 1
        lte              
        if-goto ELSE_CASE
        push constant 0
        return
      label ELSE_CASE
        push constant 2
        push argument 0
        push constant 1
        add
        push argument 1
        call sumInts       // Line 15
        add                // Line 16
        return             // Line 17
    // End of function
    

    Now I will call it:

    push constant 2
    push constant 5
    call sumInts           // Line 21
    

    The code executes and we get all the way to the stopping point where lte returns false. This is what the stack looks like at this point:

    // First invocation
    256:  2   // argument 0
    257:  5   // argument 1
    258:  21  // return line number
    259:  2   // augend
    // Second
    260:  3   // argument 0
    261:  5   // argument 1
    262:  15  // return line number
    263:  3   // augend
    // Third
    264:  4   // argument 0
    265:  5   // argument 1
    266:  15  // return line number
    267:  4   // augend
    // Fourth
    268:  5   // argument 0
    269:  5   // argument 1
    270:  15  // return line number
    271:  5   // augend
    // Fifth
    272:  6   // argument 0
    273:  5   // argument 1
    274:  15  // return line number
    275:  0   // return value
    

    Now let's "unwind" our recursion. return 0 and goto line 15 and advance.

    271:  5
    272:  0
    

    Line 16: add

    271:  5
    

    Line 17: return 5 and goto line 15 and advance.

    267:  4
    268:  5
    

    Line 16: add

    267:  9
    

    Line 17: return 9 and goto line 15 and advance.

    263:  3
    264:  9
    

    Line 16: add

    263:  12
    

    Line 17: return 12 and goto line 15 and advance.

    259:  2
    260:  12
    

    Line 16: add

    259:  14
    

    Line 17: return 14 and goto line 21 and advance.

    256:  14
    

    There you have it. Recursion: Glorified goto.

提交回复
热议问题