Following Recursive Functions

Here is a recursive function similar to the ones examined in class. It is possible to work out what it does just by looking at it and analysing it logically, but sometimes that is just too difficult. If you can't tell what a program does by examining its logic, often running it by hand, working through an example, will yield some insight. Typing it in, making the computer run it, then looking at the answer, will certainly tell you what the answer is, but is not likely to help you to understand how it works. Working through a program by hand also helps to understand exactly how recursion works, and makes you more familiar with the structure of correctly designed programs.
    int f(int a, int b)
    { int m;
      if (a==b)
        return (a);
      if (a+1==b)
        return (a*b);
      m=(a+b)/2;
      return (f(a,m)*f(m+1,b)); }
One of the first steps should be to make the job easier on ourselves. When a function has two recursive calls to itself, very close together, it can get rather confusing trying to keep track of the different points we might return to after those calls. In this case, we can simplify things by splitting the last statement into four. You should be able to convince yoursleves very quickly that the new version of f is exactly equivalent to the original in every significant way. I'll also add a main to give us a concrete beginning.
    int f(int a, int b)
    { int m, x, y, r;
      if (a==b)
        return (a);
      if (a+1==b)
        return (a*b);
      m=(a+b)/2;
      x=f(a,m);          /*  Point 'A'  */
      y=f(m+1,b);        /*  Point 'B'  */
      r=x*y;
      return (r); }

    void main(void)
    { int p=1, q=5, x;
      x=f(p,q);          /*  Point 'C'  */
      printf("f(%d,%d)=%d\n", p, q, x); }
Remember that variables declared inside functions (that is all the variables in this example) are totally private to that function. Other functions are not even aware that they exist, and have no way of accessing them. The fact that there is a variable called x in both functions is totally irrelevant to everything. main mas a private variable called x, and f has a private variable called x. They are completely separate variables, not related or connected in any way at all.
        When main calls f, f will go off and do its thing, and (we hope) eventually produce a result and finish. When f finishes, it is vital that main continues to run exactly where it left off. Similarly, each time f calls itself recursively, the recursive call will eventually finish, and we'll need to continue the original call of f from exactly where it left off. When the computer is running the program, it keeps track of that itself. When we are running it ourselves, we need to keep track of it ourselves.
        When you are about to run through a function, identify all the places where it is called, and give them names. The three comments added into the program show those names. In each case, the name refers to the exact position in the program where we should continue running after the call of f has finished. Considering point 'C', there are three clear stages: First we call f with p and q as parameters; Then f runs until it is finished; Finally the result produced by f is assigned into the variable x. It is that third stage that point 'C' refers to. Point 'C' is the exact place in the program where the result from f is about to be stored in x. It is easy to forget the exact position, and carry on as though point 'C' refers to the whole statement, but that will produce the wrong results.

        The first thing that happens when the program runs is that main is automatically called by the operating system. main is nothing special, it is just another function. A call to main is exactly the same as a call to any other function.
        When a function is called, memory has to be found for it to keep its private variables in. The compiler has already noted that main has three private variables, so the system knows to allocate four memory locations for it (enough for the variables plus one location for the "Where to come back to when we've finished" note). Remember that in most computers, and int actually requires four memory locations (a single byte would make the maximum int be 127), so the true allocation would be 16 bytes, but we will ignore these little details; they don't add anything to the exercise except unwanted complexity.
        So, a new Stack Frame of 4 memory locations is allocated for this call to main. The first location will store the "return address" reminder. When main finishes the whole program is over, so in this case the "return address" is "nowhere: just stop". The remaining three locations will be used for p, q, and x, which have no initial values.
        We don't know which memory locations are going to be used. In fact, they could be different locations each time the program is run (could be, but probably wouldn't be). Just to give a complete picture, I'll pretend that somehow I know that the first free memory location is number 12000, so that is where our first stack frame will start.
        At the exact moment when main has been called, but has not yet begun to execute, the computer's memory would look something very much like this:
AddressUseContent
Current Stack Frame12003x???
12002q???
12001p???
12000Return AddressStop
In use by O.S.11999??????
If you are interested, you can find out what addresses are being used for variables, by simply printing those variables' addresses. If we put a new statement: printf("address of p=%d\n", &p);, we would get the number 12001 printed out.
         The program runs in the normal way, using these memory locations for its variables, until we reach the point at which f is about to be called for the first time. At this point, the computer's memory will look like this:
AddressUseContent
Current Stack Frame12003x???
12002q5
12001p1
12000return addressStop
In use by O.S.11999??????

        Now f is about to be called, and absolutely nothing special happens. A new stack frame is created, this time with seven memory locations (six variables plus "return address"). The two parameters a and b are initialised with the values provided in the function call. The "return address" location is given a reference to point 'C' so we will know where to continue when f eventually finishes, and finally this new stack frame becomes the current stack frame and f starts to run.
        A function only has access to the "current" stack frame. All old stack frames are almost inaccessible, so the values of variables left in them by other functions are perfectly safe from unwanted interference. This is why it is possible for two functions to have private variables with the same names. At the exact moment when f has been called, and is ready to run, but has not yet been started, the computer's memory would look like this:
AddressUseContent
Current Stack Frame12010r???
12009y???
12008x???
12007m???
12006b5
12005a1
12004ret. add.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000ret. add.Stop
In use by O.S.11999??????
And f starts to run in the normal way. Any access to the variable x becomes an access to memory location 12008; any access to the variable b becomes an access to memory location 12006; any access to the variable p is just plain wrong, because there is no variable p in f.
        a is not equal to b, and neither is a+1, so the function continues until the point where it is ready to call the function f, in the statement x=f(a,m);. The fact that we are already in the function f is totally irrelevant. All function calls are treated equally, there are no special cases. f is called in the normal way. A new stack frame is created for it, just as it was before, the variables a and b are initialised as before (notice that by this time, m has been assigned the value 3). When this function that we are about to call is finished, it will return a result that we need to use right here. The result is to be assigned into the variable x. So the new stack frame's "return address" reminder is set to "right here", or in other words, point 'A'. The computer's memory now looks like this:
AddressUseContent
Current Stack Frame12017r???
12016y???
12015x???
12014m???
12013b3
12012a1
12011R.A.point 'A'
Old Stack Frame12010r???
12009y???
12008x???
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
And we are in an almost identical situation to the one we were in after f was called for the first time. This time, any access to the variable b will become an access to memory location 12013, and the value 3 which is stored there. There is another stack frame with its own version of b, which is still 5, but nobody cares. Only the current stack frame counts, and it says b is 3. Eventually we will return to that old stack frame, and b will return to being 5 again, but now we are using our own private b which is 3.
        And again, the function f starts to run. If you are not familiar with this kind of procedure, you might imagine that we will just keep on calling f over and over again, creating an ever taller pile of stack frames. Indeed, it is a danger with recursive programming that if you don't get the logic right, that could happen. But not here; just be patient.
        a is not equal to b, and neither is a+1; m is set to 2, and we reach another recursive call of f. There is nothing special about this. A new stack frame is created just as before, and f is ready to start running from the beginning all over again. This time, the computer's memory will look like this:
AddressUseContent
Current Stack Frame12024r???
12023y???
12022x???
12021m???
12020b2
12019a1
12018R.A.point 'A'
Old Stack Frame12017r???
12016y???
12015x???
12014m2
12013b3
12012a1
12011R.A.point 'A'
Old Stack Frame12010r???
12009y???
12008x???
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
At this point there are three different calls to the function f all still in progress. Two of them are "asleep", waiting for newer ones to finish, but they are all still alive.
        Our new call of the function f starts to run in the old familiar way. a is not equal to b, but this time a+1 is equal to b. Something different happens. We execute the statement return (a*b);, which means that first we work out what a*b is (i.e. 2) and keep the answer somewhere safe, then we abandon the current function call. f is finished. Its stack frame is no longr needed, so memory locations 12018 to 12024 a returned to the system to be recycled. Just before recycling those memory locations we remind ourselves of what we are supposed to do next. The "return address" note says "point 'A'", so that's where we go.
        What was the current stack frame has gone, so the old stack frame underneath it becomes current again. We are about to continue running from point 'A', knowing that the result of calling f was 2, and that the variable x is once again living in memory location 12015.
        And that's all there is to it. We continue running the program at point 'A', so the value 2 is stored in memory location 12015, and we continue to the next statement. At this point, the computer's memory looks like this:
AddressUseContent
Current Stack Frame12017r???
12016y???
12015x2
12014m2
12013b3
12012a1
12011R.A.point 'A'
Old Stack Frame12010r???
12009y???
12008x???
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
and we progress to the next statement y=f(m+1,b);, which is very irritatingly another call to f. By now, we should be able to handle it without any trouble at all; the procedure is exactly the same as it was every time in the past. Nothing ever changes. A new stack frame is created. It will use exactly those memory locations that we just released; recycling of memory locations is 100% efficient. The parameters m+1 and b (i.e. 3 and 3) are stored in the usual places (are you worried that one of the parameters being passed in (b) has the same name as one of the parameters in the function we are calling? I hope not, it is a total irrelevance, as always). The "return address" reminder is set to point 'B', and we are ready to start f all over again, with the computer's memory looking like this:
AddressUseContent
Current Stack Frame12024r???
12023y???
12022x???
12021m???
12020b3
12019a3
12018R.A.point 'B'
Old Stack Frame12017r???
12016y???
12015x2
12014m2
12013b3
12012a1
12011R.A.point 'A'
Old Stack Frame12010r???
12009y???
12008x???
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
This time, as soon as f starts to run, it stops. a is equal to b, so we immediately execute the statement return (a);. We carefully note that the result of this call is 3 (the value of a) and that we are supposed to return to point 'B'. Then we recycle the stack frame. The current stack frame is thrown away, the one that was buried beneath it become the current one, and we are back at point 'B', ready to store the result (3) in variable y (at location 12016). So pretty soon, we are about to execute the last two statements of f for the first time ever. Just as we start to execute r=x*y;, the computer's memory looks like this:
AddressUseContent
Current Stack Frame12017r???
12016y3
12015x2
12014m2
12013b3
12012a1
12011R.A.point 'A'
Old Stack Frame12010r???
12009y???
12008x???
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
So of course, r (at location 12017) is given the value 6, and we progress to the final statement, return (r);. What happens? Nothing special of course. We work out what r is (6) and keep it somewhere special because that is the result of this function. We look at the "return address" reminder to see where to go next (point 'A'), and throw the stack frame away.
        We are back at point 'A' again, about to store 6 in x, but this time x has reverted all the way back to memory location 12008, and we are back to having only two stack frames. Just after the result is stored in x, the computer's memory looks like this:
AddressUseContent
Current Stack Frame12010r???
12009y???
12008x6
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
We know exactly what happens next. The following statement is y=f(m+1,b);, so another new stack frame is created for f, with its parameters initialised to 4 (m+1) and 5 (b), its "return address" is set to point 'B', a reference to the assignment that this statement is supposed to to, and f is ready to start running again, with the computer's memory looking like this:
AddressUseContent
Current Stack Frame12017r???
12016y???
12015x???
12014m???
12013b5
12012a4
12011R.A.point 'B'
Old Stack Frame12010r???
12009y???
12008x6
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
And almost immediately, because a+1 is equal to b, we compute the result, a*b=20, Notice that we are supposed to go straight back to point 'B', and throw away the stack frame.
        At point 'B', the value 20 is stored in y, and we are about to execute the final statements of f again, with the computer's memory looking like this:
AddressUseContent
Current Stack Frame12010r???
12009y20
12008x6
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
It is really easy now. The next statement is r=x*y;, so r is set to 120, and we are ready for the return (r);, with memory looking like this:
AddressUseContent
Current Stack Frame12010r120
12009y20
12008x6
12007m3
12006b5
12005a1
12004R.A.point 'C'
Old Stack Frame12003x???
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
We are about to finish the very first call of f, so what happens? Nothing special. We not the value of r as the result; we note that we are supposed to continue executing from point 'C'; we destroy the stack frame.
        We are now back at point 'C', about to store the result of the function (120) in x (location 12003). Also notice that everything is nicely synchronised. Just as we jumped to point 'C', which is in the function main, we find that the current stack frame is the one belonging to main. So we are left with the computer's memory looking like this:
AddressUseContent
Current Stack Frame12003x120
12002q5
12001p1
12000R.A.Stop
In use by O.S.11999??????
Just before we print x, to see the final result, 120.
        After printing 120, main itself is finished. We notice that the thing to do next is "stop", so after destroying its stack frame (thereby reducing our use of memory to zero), that is exactly what we do. The program is finished.


You may well have noticed that the function f just calculates factorials in a slightly unusual way. If you didn't notice, don't worry. That wasn't the point of the exercise.
        If you haven't seen it yet, this is the trick. The parameters to f give a range of numbers that it is supposed to multiply together, so f(4,8) should calculate 4*5*6*7*8. The original call in main: f(1,5) calculates 1*2*3*4*5 which is of course the factorial of 5.
        The way it does the calculation is by splitting the range roughly in half, so a calculation of 1*2*3*4*5*6*7*8*9 would be split into two separate calculations of 1*2*3*4*5 and 6*7*8*9, which would then be multiplied together to form the complete answer. That is all that f does. It calculates m to be the midpoint between a and b, so that the first half of the calculation works on the numbers from a to m, and the second works on the rest, from m+1 to n.
        The two ifs at the beginning simply make sure that the recursion eventually ends. The first one says (for example) that the result of mutliplying together all the numbers between 6 and 6 is 6 itself; there is no work to be done. The second one says that is you want to multiply together all the numbers between 5 and 6, just work out 5*6; don't waste time splitting such a small range into two even smaller ones.