A stack is a LIFO data structure (last in, first out, meaning last entry you push on to the stack is the first one you get back when you pop). It is typically used to hold stack frames (bits of the stack that belong to the current function).
This may include, but is not limited to:
- the return address.
- a place for a return value.
- passed parameters.
- local variables.
You push items onto the stack and pop them off. In a microprocessor, the stack can be used for both user data (such as local variables and passed parameters) and CPU data (such as return addresses when calling subroutines).
The actual implementation of a stack depends on the microprocessor architecture. It can grow up or down in memory and can move either before or after the push/pop operations.
Operation which typically affect the stack are:
- subroutine calls and returns.
- interrupt calls and returns.
- code explicitly pushing and popping entries.
- direct manipulation of the stack pointer register,
sp.
Consider the following program in my (fictional) assembly language:
Addr Opcodes Instructions ; Comments
---- -------- -------------- ----------
; 1: pc<-0000, sp<-8000
0000 01 00 07 load r0,7 ; 2: pc<-0003, r0<-7
0003 02 00 push r0 ; 3: pc<-0005, sp<-7ffe, (sp:7ffe)<-0007
0005 03 00 00 call 000b ; 4: pc<-000b, sp<-7ffc, (sp:7ffc)<-0008
0008 04 00 pop r0 ; 7: pc<-000a, r0<-(sp:7ffe[0007]), sp<-8000
000a 05 halt ; 8: pc<-000a
000b 06 01 02 load r1,[sp+2] ; 5: pc<-000e, r1<-(sp+2:7ffe[0007])
000e 07 ret ; 6: pc<-(sp:7ffc[0008]), sp<-7ffe
Now let’s follow the execution, describing the steps shown in the comments above:
-
This is the starting condition where
pc(the program counter) is0andspis8000(all these numbers are hexadecimal). -
This simply loads register
r0with the immediate value7and movespcto the next instruction (I’ll assume that you understand the default behavior will be to move to the next instruction unless otherwise specified). -
This pushes
r0onto the stack by reducingspby two then storing the value of the register to that location. -
This calls a subroutine. What would have been
pcin the next step is pushed on to the stack in a similar fashion tor0in the previous step, thenpcis set to its new value. This is no different to a user-level push other than the fact it’s done more as a system-level thing. -
This loads
r1from a memory location calculated from the stack pointer – it shows a way to pass parameters to functions. -
The return statement extracts the value from where
sppoints and loads it intopc, adjustingspup at the same time. This is like a system-levelpopinstruction (see next step). -
Popping
r0off the stack involves extracting the value from wherespcurrently points, then adjustingspup. -
The
haltinstruction simply leavespcwhere it is, an infinite loop of sorts.
Hopefully from that description, it will become clear. Bottom line is: a stack is useful for storing state in a LIFO way and this is generally ideal for the way most microprocessors do subroutine calls.
Unless you’re a SPARC of course, in which case you use a circular buffer for your stack 🙂
Update: Just to clarify the steps taken when pushing and popping values in the above example (whether explicitly or by call/return), see the following examples:
LOAD R0,7
PUSH R0
Adjust sp Store val
sp-> +--------+ +--------+ +--------+
| xxxx | sp->| xxxx | sp->| 0007 |
| | | | | |
| | | | | |
| | | | | |
+--------+ +--------+ +--------+
POP R0
Get value Adjust sp
+--------+ +--------+ sp->+--------+
sp-> | 0007 | sp->| 0007 | | 0007 |
| | | | | |
| | | | | |
| | | | | |
+--------+ +--------+ +--------+