Recursion Notes

Programs normally run from the top to the bottom. We need ways to alter this flow of control. We have used several control mechanisms in our programs so far. We have used while loops, for loops, do loops and if statements. Recursion is another control structure that can be used to control the flow of a program.

It is important to match the structure of a program to its design. If we are writing a program to look at all the elements in an array, the design says to iterate from 0 to the size of the array. So, a for loop is called for since it looks like the design.

Other kinds of problems are matched by other kinds of control structures. We will see problems, especially involving trees, where recursion is a good match the problem.

To understand recursion, it is necessary to understand something about how programs are compiled and run. In particular, subroutine jumps are key to understanding recursion.

Compilation

A compiler translates your program from the source language to the target language. In our case, from C++ to assembler language. To assist in this effort, the compiler keeps an internal database of information called the symbol table. Given a bit of code like this

int
F1: foo(int x)
{
	return(x+2);
}
for this bit, the information recorded would include:
Field Name Value
Kind Function
Name foo
Param int
Formal name x
Return Type int
Location F1

Program Flow

A program ultimately is a sequence of instructions in memory along with data. The flow of control through a program is controlled by a special memory location called the program counter The PC holds the memory address of the instruction to be executed next. When control is changed, the PC is set to the address containing the instruction we want to execute next.

Subroutine Jump

Another kind of flow control is a subroutine jump. A subroutine is a sequence of instructions with data. Essentially it is a small program. It is stored in memory in a different section from the main program. Consider this bit of code

main()
{
// some code goes here
L0:y = foo(7);
L1:cout << y;
// some more code
}

When it comes time to execute the subroutine foo, there are several things that must happen. We need to record where we are in the program so we can come back when the subroutine is done. This is called the return address. We also need to transfer the parameters to the new program. To do this, we build a data structure called a stack frame. As the name implies, this involves a stack. We use a stack to hold this information to handle nested function calls. If we have the following code:

main()
{
	x = funca(7);
}
int funca(int x)
{
	int y = funcb(x);
	return(y);
}

int
funcb(int z)
{
	return(z + 2);
}

When funca() is called in main, we record where it came from. Then funca() calls funcb(). And again, we record the location. At the end of funcb(), we have to retrieve the return point information we saved before the call to funcb() from funca(). Then, at the end of funca(), we have to retrieve the information we saved when we called funca() from main.

If we used a single place to save the information, the data from the call to funca() from main would be replaced by the data from the call to funcb() from funca(). But if we push the information onto a stack, then, when we return from funcb(), we pop the top information off the stack. Now when we return from funca(), we again pop the information from the stack and go back to main.

So a stack is used to hold the data from a subroutine call. When we execute the call to foo() from main, in the first example code, we create a stack frame. In this case, the frame has two pieces of information. It contains the return address of foo L1 and the value of the parameter, in this case, 7. The PC is set to the address of the function, in this case, F1. Then we start running the code in foo(). Before the first line that we wrote is executed, the parameter value is copied from the stack into a local variable for the function. This local variable is the formal parameter of the function, in this case x. when the function is done, the stack frame is popped from the stack.

The return address is remembered and the value of the return statement is pushed onto the stack. Then the PC is set to the return address and execution picks up where we left it when the subroutine jump was made.

In main, we had a line at L0 that looked like

y = foo(7);
Before we go one to line L1, the value on the top of the stack is copied to the variable y. Now the entire process is complete and the program goes on from line L1.