Section 10.2 The Stack
The stack is used extensively for the interface between a calling function and called function, local variables created within a function, and saving items within a function. Before describing how these things are done, we need to understand what stacks are and how they are used. A stack is an area of memory for storing data items together with a pointer to the โtopโ of the stack. Informally, you can think of a stack as being organized very much like a stack of dinner plates on a shelf. We can only access the one item at the top of the stack. (And, yes, if you pull out a plate from somewhere with the stack, you will probably break something.) There are two fundamental operations on a stack:- push data-item
This causes the data-item to be placed on the top of the stack and moves the stack pointer to point to this latest item.
- pop location
The data item on the top of the stack is moved to location and the stack pointer is moved to point to the next item left on the stack.
push red plate
push green plate
push blue plate
pop kitchen-counter
Always push an item onto the stack before popping anything off.
Never pop more things off than you have pushed on.
Always pop everything off the stack.
- Ascending Stack
Grows toward numerically higher memory addresses.
- Descending Stack
Grows toward numerically lower memory addresses.
- Full Stack
The stack pointer points to the location of the last item that was stored on the stack. (The location is โfullโ.)
- Empty Stack
The stack pointer points to the location where the next item will be stored on the stack. (The location is โemptyโ.)
/* stack.c * Stack example in C. * 2017-09-29: Bob Plantz */ /* Prototype declarations for functions used in this function. */ #include <stdio.h> #include "push.h" #include "pop.h" /* The stack and stack pointer are global variables. They * are accessible to all functions in the program. */ int theStack[500]; int *stackPointer = &theStack[500]; /* main * Illustrates the actions of the push and pop operations. */ int main(void) { int x = 12; int y = 34; int z = 56; printf("Start with the stack pointer at %p\n", (void *)stackPointer); printf("x = %i, y = %i, and z = %i\n", x, y, z); push(x); push(y); push(z); x = 100; y = 200; z = 300; printf("push x\npush y\npush z\n"); printf("Now the stack pointer is at %p\n", (void *)stackPointer); printf("Change x, y, and z:\n"); printf("x = %i, y = %i, and z = %i\n", x, y, z); pop(&z); pop(&y); pop(&x); printf("pop z\npop y\npop x\n"); printf("And we end with the stack pointer at %p\n", (void *)stackPointer); printf("x = %i, y = %i, and z = %i\n", x, y, z); return 0; }
main
function uses the push
and pop
functions in Listings 10.2.2โ10.2.5./* push.h * implementation of push operation in C * 2017-09-29: Bob Plantz */ /* The stack and stack pointer are global variables. They * are accessible to all functions in the program. */ extern int *stackPointer; /* push operation: * Address in stackPointer is decremented by size * of int. dataValue is stored at this address, which * is now the top of the stack. */ #ifndef PUSH_H #define PUSH_H void push(int dataValue); #endif
push
function./* push.c * implementation of push operation in C * 2017-09-29: Bob Plantz */ #include "push.h" void push(int dataValue) { stackPointer--; *stackPointer = dataValue; }
push
function./* pop.h * pop stack operation in C * 2017-09-29: Bob Plantz */ /* The stack and stack pointer are global variables. They * are accessible to all functions in the program. */ extern int *stackPointer; /* pop operation: * Copies int at current location of stack pointer * to *dataLocation; address in stackPointer is * incremented by size of int. */ #ifndef POP_H #define POP_H void pop(int *dataLocation); #endif
pop
function./* pop.c * pop stack operation in C * 2017-09-29: Bob Plantz */ #include "pop.h" void pop(int *dataLocation) { *dataLocation = *stackPointer; stackPointer++; }
pop
function.Both the stack and the stack pointer are global variables. This mimics the situation with the stack you will use in your assembly language programs. The memory for the stack and the stack pointer register,
sp
, are globally accessible from any function in your program.The stack pointer always points to the item that is currently at the top of the stack.
A push operation pre-decrements the stack pointer before storing an item on the stack. Hence the program initializes the stack pointer to point one item beyond the highest numbered element in the array that makes up the stack. The stack โgrowsโ from higher addresses to lower addresses as items are pushed onto it.
A pop operation post-increments the stack pointer after retrieving an item from the stack, thus mirroring the effect of a push operation.
int *stackPointer = &theStack[500];
push(x);
x
, y
, and z
โare pushed onto the stack, it appears as shown in Figure 10.2.8. The stack pointer always points to the data item that is at the top of the stack.
push
and pop
instructions in the AARCH32 architecture use the sp
(r13
) register for the stack pointer. A push
causes the address in this register to decrease, and a pop
causes the address to increase. Thus the stack grows from high addresses to low. Although this addressing scheme may seem a bit odd at first, there are some good reasons for doing it this way.
Think about how you might organize things in memory. Recall that the program counter is automatically incremented by the control unit as your program is executed. Programs come in vastly different sizes, so it makes sense to store the program instructions at low memory addresses. This allows maximum flexibility with respect to program size.
The stack is a dynamic structure. You do not know ahead of time how much stack space will be required by any given program as it executes. It is impossible to know how much space to allocate for the stack. So you would like to allocate as much space as possible, while preventing it from colliding with program instructions. The solution is to start the stack at the highest address and have it grow toward lower addresses.
This is a highly simplified rationalization for implementing stacks such that they grow โdownwardโ in memory. The organization of various program elements in memory is much more complex than the simple description given here. But this may help you to understand that there are some good reasons for what may seem to be a rather odd implementation.