The chapter began with an illustration suggesting that functions can call one another, forming a sequence or chain of function calls. The sequence length can be quite long, limited only by the program's memory, and the functions return in the opposite order of their calls. Recursion describes the case where a sequence of function calls form a cycle. It's easier to understand and classify recursion with two pseudo-code examples.
Direct Recursion | Indirect Recursion | |||
---|---|---|---|---|
void f() { . . . f(); . . . } |
void a() { . . . b(); . . . } |
void b() { . . . c(); . . . } |
void c() { . . . d(); . . . } |
void d() { . . . a(); . . . } |
(a) | (b) |
Computer scientists use indirect recursion to solve complex problems such as general expression evaluation or even to implement some kinds of compilers. Nevertheless, this introductory section will focus only on direct or simple recursion.
Recursively calling a general function, one not specifically designed to operate recursively causes runaway recursion - an infinitely long chain of recursive function calls - a logical and ultimately fatal error. In addition to the operations needed to solve a specific problem, three features are necessary for recursion.
There is a fourth requirement for recursion, but it is a programming language feature and is, therefore, beyond the programmer's control or concern. The programming language must support automatic variables, as the following explains.
Two simple, related functions demonstrate recursion. Aside from their names, the function's only difference is the statement order in the if-statement. This seemingly insignificant difference is sufficient to cause the two functions to print the digits of their parameters in different orders.
void forward(int number) { if (number != 0) { forward(number / 10); cout << number % 10; } } |
void reverse(int number) { if (number != 0) { cout << number % 10; reverse(number / 10); } } |
forward(123) prints 123 | reverse(123) prints 321 |
number != 0
, is false, the function returns without further recursion. The forward function returns to the cout statement in the previous call.number / 10
, changes for each recursive call; eventually, the argument goes to 0, making the if-statement false, ending the recursionWhen a program calls a function, we say the function is active, and the program creates an activation record - also known as a stack frame - and pushes it on the runtime stack. When the function returns, the program pops the record or frame off the stack. When one function calls another, the first remains active while the second runs - each has a stack frame on the stack. In the case of a recursive function call, the function has multiple activations and multiple stack frames on the runtime stack.
Matching recursive function calls with simplified stack frames is one way to understand recursion. Each frame holds the return address and all the function's local, automatic variables, including its parameters. When the function ends, its frame is popped off and discarded, deallocating the memory for the automatic variables. The following figure illustrates the articulation between the stack frames and the recursive function calls.