• No results found

Exit: ldr lr, =return lrd lr, [lr] @ standard return to OS bx lr @ .data

list: .space 400 @ room for 100 integers return: .word 0 @ save return address format: .asciz " %d "

@

/* External */ .global printf

When we run this code we find 100 integers stored inarrayin the reverse order to which they were printed (for no obvious reason). While they may look somewhat “random”, they obviously cannot be truly random numbers since we will get exactly the same results every time we run this code! Again it is suggested that a careful reading of Knuth’s material would be helpful and will justify a recommendation that any random number generator a programmer uses (including those in expensive packages) should be tested as much as possible.

Note that the loads during the initialization ofmainare carried out in three steps rather than the commented out one instruction. If one tries to assemble the obvious instruction

ldr r0, #31416, one gets an error message of some kind. On the Raspberry Pi it not only gives the error message but also explains what number (in hex) it was unable to put in the part of the ldr - immediate machine instruction that stores the number. With the three instructions shown, we can obtain the desired result. You will investigate immediate operands in a project.

10.4

More Debugging

Now that our programs are much more complicated, it is harder and harder to debug them. There are a few additional helpful things gdb can do to assist us.

For example, the gdb command x/Nuf expression, where x stands for eXamine, will display the contents of N units (u) of memory (where units could be b for byte or w

for word - four bytes) and f gives the format (which could be x for hexadecimal, c for character, u for unsigned decimal, and some others) at the location described by the

expression.

Another important help is the ability to set breakpoints that are locations at which execution will stop and wait for the user to do whatever will give them information about the registers and memory at that point of the program. They are set by the

10. Searching and Sorting

command break line or break function (and other methods) where the line is a line number from the original program and function is a label.

We now see why, in Chapter 1, we called on the assembler as with the parameter -g. That asked the assembler to keep all the debugging information available for gdb to be able to work with the original source code.

Projects

1. Theisort.s code has been written in a form where it is obvious that the actual sorting code could have been separated out as a function taking the address of the array and its length as parameters (in r0 and r1). Rewrite the code in that way. 2. Looking at the ASCII table in Appendix A, notice the values of the different

characters. Modify the isort.s code to act on the characters in a string.

3. There are many obvious inefficiencies in the programs given as examples in this chapter. Improve some of the obvious ones.

4. Rewriterand.sso that it takes input from the user as the first “seed”. Run many tests of the rand program with different seeds.

5. Expand the usefulness of rand by allowing the user to pick the range of values returned by the program and also how many values it returns.

6. Save the results fromrand by calling the program using./rand > data.dat and work on the file data.dat that is created.

7. Make the data file data.dat the input to the isort program by calling it with

./isort < data.dat > sorted.datand check the results in the filesorted.dat. Note that you must append a negative number to the end of data.dat to signal the end of input to isort.

8. Practice using gdb to watch what is an array as the program runs. Set some breakpoints to get snapshots of what is in the various registers as the program progresses. Note that labels are treated a function names by the debugger. 9. Try other three instruction loads similar to those used in rand.s. Using gdb,

watch the behavior of the memory locations.

10. Investigate howimmediate operands are formed by looking at detailed documen- tation.

11 Recursion and the Stack

In Chapter 9 we were introduced to functions and we saw that they have to follow a number of conventions in order to articulate with other functions. We also briefly mentioned the stack as an area of memory owned solely by the function. In this chapter we will consider the stack in more depth and indicate why it is important for functions.

11.1

Dynamic activation

One of the benefits of functions is being able to call them more than once. But that more than once hides a small trap. We are not restricting who will be able to call the function, so it might happen that it is the same function that calls itself. When this happens we call it recursion.

The idea of a recursive function, one that may call on itself, is paradoxical for many reasons. Many programmers consider recursion a difficult concept but actually it is often so simple that it should be used for rapid prototyping (quickly writing some possibly poor code which can be used to demonstrate a system). Some of the most basic methods of software engineering are based on recursion.

A typical example of recursion is calculating the factorialof a numbern, usually written as n! . It is defined on the non-negative integers by the mathematical formula

0! = 1 and

n! = n * (n-1)! for n > 0

Recursive code for the function factorial can immediately be written in C as follows:

int factorial(int n) {

if (n == 0) return 1; return n * factorial(n-1); }

This example is characteristic of recursive function translation from mathematical def- inition to high-level language. If a function is given in recursive form the translation is almost automatic. Of course one should be careful to check that the original definition

11. Recursion and the Stack

is correct and should note restrictions on the type and range of input variables. In our factorial example the function is only correctly defined for non-negative integers. A recursive definition of a function always includes some base cases which, for some reason, are so trivial that their values are known or can be calculated without further recursion. The recursive cases, on the other hand, must make some progress toward the base cases. This means that the arguments for the recursive call are usually smaller in some sense (possibly smaller sets rather than always smaller integers). All our examples will be such that they are independent of the language we use.

Note that there is only one function factorial, but it may be called several times. For instance: factorial(3) → factorial(2) → factorial(1) → factorial(0), where → means “calls”. A function, thus, is dynamically activated each time it is called. The span of a dynamic activation goes from the point where the function is called until it returns. At a given time, more than one function can be dynamically activated. The whole dynamic activation set of functions includes the current function and the dynamic activation set of the function that called it (the current function).

We now have a function that calls itself. That would not be a problem if it weren’t for the rules that a function must observe. Let’s quickly recall them.

• Only r0, r1, r2 and r3 can be freely modified.

• The lrvalue at the entry of the function must be kept somewhere because we will need it to leave the function (to return to the caller).

• All other registers r4 tor11 and sp may be modified but they must be restored to their original values upon leaving the function.

In Chapter 9 we used a global variable to save lr. But if we attempted to use a global variable in our f actorial(3) example, it would be overwritten at the next dynamic acti- vation offactorial. We would only be able to return fromf actorial(0) tof actorial(1). After that we would be stuck in f actorial(1), as lr would always have the same value. So it looks like we need some way to keep at least the value of lr for each dynamic activation. And not only lr; if we wanted to use registers from r4 to r11 we would also need to keep them somehow for each dynamic activation. Clearly, global variables would not be enough either. This is where the stack comes into play.