RECURSIVE FUNCTIONS

Recursive Functions
The next program will stretch your brains even more. The program will compute
the factorial of a number. A factorial is the product of a number and all the
numbers between it and one. For example, the factorial of 7 is 7*6*5*4*3*2*1,
and the factorial of 4 is 4*3*2*1. Now, one thing you might notice is that the
factorial of a number is the same as the product of a number and the factorial just
below it. For example, the factorial of 4 is 4 times the factorial of 3. The factorial
of 3 is 3 times the factorial of 2. 2 is 2 times the factorial of 1. The factorial of 1 is
1. This type of definition is called a recursive definition. That means, the definition
of the factorial function includes the factorial funtion itself. However, since all
functions need to end, a recursive definition must include a base case. The base
case is the point where recursion will stop. Without a base case, the function
would go on forever calling itself until it eventually ran out of stack space. In the
case of the factorial, the base case is the number 1. When we hit the number 1, we
don’t run the factorial again, we just say that the factorial of 1 is 1. So, let’s run
through what we want the code to look like for our factorial function:
1. Examine the number
2. Is the number 1?
3. If so, the answer is one
4. Otherwise, the answer is the number times the factorial of the number minus
one
This would be problematic if we didn’t have local variables. In other programs,
storing values in global variables worked fine. However, global variables only
provide one copy of each variable. In this program, we will have multiple copies
64
Chapter 4. All About Functions
of the function running at the same time, all of them needing their own copies of
the data!6 Since local variables exist on the stack frame, and each function call
gets its own stack frame, we are okay.
Let’s look at the code to see how this works:
#PURPOSE - Given a number, this program computes the
# factorial. For example, the factorial of
# 3 is 3 * 2 * 1, or 6. The factorial of
# 4 is 4 * 3 * 2 * 1, or 24, and so on.
#
#This program shows how to call a function recursively.
.section .data
#This program has no global data
.section .text
.globl _start
.globl factorial #this is unneeded unless we want to share
#this function among other programs
_start:
pushl $4 #The factorial takes one argument - the
#number we want a factorial of. So, it
#gets pushed
call factorial #run the factorial function
addl $4, %esp #Scrubs the parameter that was pushed on
#the stack
movl %eax, %ebx #factorial returns the answer in %eax, but
#we want it in %ebx to send it as our exit
#status
6. By "running at the same time" I am talking about the fact that one will not have finished
before a new one is activated. I am not implying that their instructions are running at the
same time.
65
Chapter 4. All About Functions
movl $1, %eax #call the kernel’s exit function
int $0x80
#This is the actual function definition
.type factorial,@function
factorial:
pushl %ebp #standard function stuff - we have to
#restore %ebp to its prior state before
#returning, so we have to push it
movl %esp, %ebp #This is because we don’t want to modify
#the stack pointer, so we use %ebp.
movl 8(%ebp), %eax #This moves the first argument to %eax
#4(%ebp) holds the return address, and
#8(%ebp) holds the first parameter
cmpl $1, %eax #If the number is 1, that is our base
#case, and we simply return (1 is
#already in %eax as the return value)
je end_factorial
decl %eax #otherwise, decrease the value
pushl %eax #push it for our call to factorial
call factorial #call factorial
movl 8(%ebp), %ebx #%eax has the return value, so we
#reload our parameter into %ebx
imull %ebx, %eax #multiply that by the result of the
#last call to factorial (in %eax)
#the answer is stored in %eax, which
#is good since that’s where return
#values go.
end_factorial:
movl %ebp, %esp #standard function return stuff - we
popl %ebp #have to restore %ebp and %esp to where
#they were before the function started
ret #return to the function (this pops the
66
Chapter 4. All About Functions
#return value, too)
Assemble, link, and run it with these commands:
as factorial.s -o factorial.o
ld factorial.o -o factorial
./factorial
echo $?
This should give you the value 24. 24 is the factorial of 4, you can test it out
yourself with a calculator: 4 * 3 * 2 * 1 = 24.
I’m guessing you didn’t understand the whole code listing. Let’s go through it a
line at a time to see what is happening.
_start:
pushl $4
call factorial
Okay, this program is intended to compute the factorial of the number 4. When
programming functions, you are supposed to put the parameters of the function on
the top of the stack right before you call it. Remember, a function’s parameters are
the data that you want the function to work with. In this case, the factorial function
takes 1 parameter - the number you want the factorial of.
The pushl instruction puts the given value at the top of the stack. The call
instruction then makes the function call.
Next we have these lines:
addl $4, %esp
movl %eax, %ebx
movl $1, %eax
67
Chapter 4. All About Functions
int $0x80
This takes place after factorial has finished and computed the factorial of 4 for
us. Now we have to clean up the stack. The addl instruction moves the stack
pointer back to where it was before we pushed the $4 onto the stack. You should
always clean up your stack parameters after a function call returns.
The next instruction moves %eax to %ebx. What’s in %eax? It is factorial’s
return value. In our case, it is the value of the factorial function. With 4 as our
parameter, 24 should be our return value. Remember, return values are always
stored in %eax. We want to return this value as the status code to the operating
system. However, Linux requires that the program’s exit status be stored in %ebx,
not %eax, so we have to move it. Then we do the standard exit system call.
The nice thing about function calls is that:
• Other programmers don’t have to know anything about them except it’s
arguments to use them.
• They provide standardized building blocks from which you can form a program.
• They can be called multiple times and from multiple locations and they always
know how to get back to where they were since call pushes the return address
onto the stack.
These are the main advantages of functions. Larger programs also use functions to
break down complex pieces of code into smaller, simpler ones. In fact, almost all
of programming is writing and calling functions.
Let’s now take a look at how the factorial function itself is implemented.
Before the function starts, we have this directive:
.type factorial,@function
factorial:
68
Chapter 4. All About Functions
The .type directive tells the linker that factorial is a function. This isn’t really
needed unless we were using factorial in other programs. We have included it
for completeness. The line that says factorial: gives the symbol factorial
the storage location of the next instruction. That’s how call knew where to go
when we said call factorial.
The first real instructions of the function are:
pushl %ebp
movl %esp, %ebp
As shown in the previous program, this creates the stack frame for this function.
These two lines will be the way you should start every function.
The next instruction is this:
movl 8(%ebp), %eax
This uses base pointer addressing to move the first parameter of the function into
%eax. Remember, (%ebp) has the old %ebp, 4(%ebp) has the return address, and
8(%ebp) is the location of the first parameter to the function. If you think back,
this will be the value 4 on the first call, since that was what we pushed on the stack
before calling the function (with pushl $4). parameter into %eax. As this
function calls itself, it will have other values, too.
Next, we check to see if we’ve hit our base case (a parameter of 1). If so, we jump
to the instruction at the label end_factorial, where it will be returned. Et’s
already in %eax which we mentioned earlier is where you put return values. That
is accomplished by these lines:
cmpl $1, %eax
je end_factorial
If it’s not our base case, what did we say we would do? We would call the
factorial function again with our parameter minus one. So, first we decrease
%eax by one:
69
Chapter 4. All About Functions
decl %eax
decl stands for decrement. It subtracts 1 from the given register or memory
location (%eax in our case). incl is the inverse - it adds 1. After decrementing
%eax we push it onto the stack since it’s going to be the parameter of the next
function call. And then we call factorial again!
pushl %eax
call factorial
Okay, now we’ve called factorial. One thing to remember is that after a
function call, we can never know what the registers are (except %esp and %ebp).
So even though we had the value we were called with in %eax, it’s not there any
more. Therefore, we need pull it off the stack from the same place we got it the
first time (at 8(%ebp)). So, we do this:
movl 8(%ebp), %ebx
Now, we want to multiply that number with the result of the factorial function. If
you remember our previous discussion, the result of functions are left in %eax. So,
we need to multiply %ebx with %eax. This is done with this instruction:
imull %ebx, %eax
This also stores the result in %eax, which is exactly where we want the return
value for the function to be! Since the return value is in place we just need to leave
the function. If you remember, at the start of the function we pushed %ebp, and
moved %esp into %ebp to create the current stack frame. Now we reverse the
operation to destroy the current stack frame and reactivate the last one:
end_factorial:
movl %ebp, %esp
popl %ebp
Now we’re already to return, so we issue the following command
70
Chapter 4. All About Functions
ret
This pops the top value off of the stack, and then jumps to it. If you remember our
discussion about call, we said that call first pushed the address of the next
instruction onto the stack before it jumped to the beginning of the function. So,
here we pop it back off so we can return there. The function is done, and we have
our answer!
Like our previous program, you should look over the program again, and make
sure you know what everything does. Look back through this section and the
previous sections for the explanation of anything you don’t understand. Then, take
a piece of paper, and go through the program step-by-step, keeping track of what
the values of the registers are at each step, and what values are on the stack. Doing
this should deepen your understanding of what is going on.
Review
Know the Concepts
• What are primitives?
• What are calling conventions?
• What is the stack?
• How do pushl and popl affect the stack? What special-purpose register do
they affect?
• What are local variables and what are they used for?
• Why are local variables so necessary in recursive functions?
• What are %ebp and %esp used for?
• What is a stack frame?
71
Chapter 4. All About Functions
Use the Concepts
• Write a function called square which receives one argument and returns the
square of that argument.
• Write a program to test your square function.
• Convert the maximum program given in the Section called Finding a Maximum
Value in Chapter 3 so that it is a function which takes a pointer to several values
and returns their maximum. Write a program that calls maximum with 3
different lists, and returns the result of the last one as the program’s exit status
code.
• Explain the problems that would arise without a standard calling convention.
Going Further
• Do you think it’s better for a system to have a large set of primitives or a small
one, assuming that the larger set can be written in terms of the smaller one?
• The factorial function can be written non-recursively. Do so.
• Find an application on the computer you use regularly. Try to locate a specific
feature, and practice breaking that feature out into functions. Define the
function interfaces between that feature and the rest of the program.
• Come up with your own calling convention. Rewrite the programs in this
chapter using it. An example of a different calling convention would be to pass
paramters in registers rather than the stack, to pass them in a different order, to
return values in other registers or memory locations. Whatever you pick, be
consistent and apply it throughout the whole program.
• Can you build a calling convention without using the stack? What limitations
might it have?
72
Chapter 4. All About Functions
• What test cases should we use in our example program to check to see if it is
working properly?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值