5.8. More on Using the Stack
Now it's time to get back to our analysis of the use of the stack in storing information needed during execution of a function. The next statement in our example program (
Figure 5.5 on page 239) is
Result = (First + Second) / 2;. Since we've assumed that
First is 2, and
Second is 4, the value of
Result will be (4+2)/2, or 3. After this statement is executed, the stack looks like
Figure 5.22.
FIGURE 5.22.
| Address |
Contents |
Meaning |
| 20001ff2 |
???? |
none |
| 20001ff4 |
0003 |
Result |
| 20001ff6 |
10001005 |
return address in main |
| 20001ffa |
0004 |
Second |
| 20001ffc |
0002 |
First |
| 20001ffe |
???? |
none |
The stack after the initialization of
Result
Finally, at the end of the function, the stack pointer will be incremented to point to the stored return address. Then the
return instruction will reload the program counter with the stored return address, which in this case is
10001005. Then the value of
Result will be made available to the calling function and the stack pointer will be adjusted so the stack looks as it did before we called
Average.
After the return, the stack will be empty as we no longer need the arguments, the
auto variable
Result, or the return address from the
Average function.
Figure 5.23 shows what the stack looks like now.
FIGURE 5.23.
| Address |
Contents |
Meaning |
| 20001ff2 |
???? |
none |
| 20001ff4 |
0003 |
none |
| 20001ff6 |
10001005 |
none |
| 20001ffa |
0004 |
none |
| 20001ffc |
0002 |
none |
| 20001ffe |
???? |
none |
The stack after exiting from
Average
Do not be fooled by the casual statement "the stack is empty". That means only that the stack pointer (
esp) is pointing to the same place it was when we started our excursion into the
Average function; namely,
20001ffe. The values that were stored in the memory locations used by
Average for its
auto variables haven't been erased by changing the stack pointer. This illustrates one very good reason why we can't rely on the values of
auto variables until they've been initialized; we don't know how the memory locations they occupy might have been used previously.
The previous discussion of how arguments are copied into local variables when a function is called applies directly to our
Average function. If we try to change the input arguments, we will change only the copies of those arguments on the stack and the corresponding variables in the calling function won't be altered.
1 That's perfectly acceptable here, since we don't want to change the values in the calling function; we just want to calculate their average and provide the result to the calling function. An argument that is handled this way is called a
value argument, as its value is copied into a newly created variable in the called function, rather than allowing the called function access to the "real" argument in the calling function.
2
One thing we haven't really discussed here is how the return value gets back to the caller. One way is to store it in a register, which is then available to the calling routine after we get back. This is a very easy and fast way to pass a return value back to the caller. However, it has a drawback: a register can hold only one value of 32 bits. Sometimes this is not enough, in which case another mechanism will be used. However, the compiler takes care of these details for us, so we don't have to worry about them.
1
Actually, it's generally a good idea not to change the values of arguments, even value arguments. Although this is legal, it tends to confuse programmers who read your code later as they often make the implicit assumption that arguments have the same value throughout a function.
2
It's also possible to define a function that has access to an actual variable in the calling function; we'll see how and when to do that at the appropriate time.