The Stack: Last in, First out

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
A stack is data structure that deals with data in a "Last In First Out" manner. This just means when you try to retrieve an element from the stack, it will give you the element that was most recently given to it. But what is the stack used for, and why is it so important? The most common real-world comparison of a stack is just a stack of papers on a desk. When you add paper to the stack, it gets placed on top of all the other papers, and if you want to pick up a piece of paper, you can only grab the one that's on top. These are the two basic operations of a stack: PUSH, adding to the stack, and POP or PULL, removing from the stack. And that's about it. The concept of a stack is very simple, but just how much it helps with data and execution management far exceeds its simplicity. This is what makes the stack so great. If this was all you knew, you might be a little confused. If the system you are using allows you to store data anywhere in memory, where ever you'd like, why would you ever use the stack? The ability to access data in the stack seems pretty limited, and it just seems like a nusance. Well, the stack wasn't meant to be used for most data management, but what was built for, it does really well. Before we go into depth about what it was used for, we should describe how it was implemented. First off, the stack is located somewhere in main memory. It doesn't exist as its own special entity--all of the data stored in the stack is in memory somewhere, so it can just be accessed just like any other value in memory. So even though we said before that you can only access the value on top of the stack, that just refers to the PUSH and POP operations. POP will remove the top-most element from the stack, and PUSH places a new element above the current top-most element. All of the elements can be read or written to at any time, but the size of the stack will not change unless a PUSH or POP occurs. The main register that dictates how large the stack is at any one point is called the Stack Pointer. The stack pointer holds the memory address that acts as the border between values that are part of the stack and those that are not. There are actually two ways the stack pointer can be interpreted. It can hold the memory address of the current top-most element on the stack. This is called the full stack convention. Likewise, it can hold the memory address of the first byte not included in the stack. This is called the empty stack convention. These two different conventions work perfectly fine, they just act slightly different when a PUSH and POP operation happens, and it is important to know which is being used. In the full stack convention, the stack pointer moves before PUSHing and after POPing, while in the empty stack convention it moves after PUSHing and before POPing. Most systems use the empty stack convention, so that is what will be used in this video. As far as how the bytes in the stack correspond to bytes in main memory, there are again two different ways of doing it. In an ascending stack, the stack pointer is increased when elements are PUSHed to it, and decreased when elements are POPed from it. In a desending stack, the exact opposite is the case. The stack pointer is decreased when elements are PUSHed, and increased when elements are POPed. Again, most systems use a descending stack, so that will be illustrated here. Now with all of this knowledge, let's revisit the PUSH and POP operations. We'll suppose the stack pointer is already at some address, let's say $01D0. This is a descending stack, so we know data in memory locations higher than this are part of the stack, and memory locations lower than this are considered empty. It also uses the empty stack convention, so we know that the memory address $01D0 is empty, and that the data in $01D1 is the top-most element. Let's PUSH the byte #$62 onto the stack. First the value #$62 will be placed in memory referenced by the stack pointer, $01D0. Then, the stack pointer is decremented by one. So it becomes $01CF. If we wanted to POP that byte back off of the stack, we just do the same thing, but in reverse. First the stack pointer is incremented by one, so it becomes $01D0. Then the value #$62 is read into a processor register. Note that the value in memory doesn't actually get erased or modified, only the stack pointer is modified during a POP operation. The memory address is still considered empty however, since it will get overwritten by another byte that is PUSHed onto the stack. Now since this is all just memory, we can read and modify anything we want if we choose. We don't have to use the PUSH and POP operations to do everything. The stack pointer is just another processor register, so we can use the memory address it references as an argument into another instruction. So if we wanted to read the third-highest element in the stack, we can just take the value of the stack pointer, add 3, and then read the memory address referenced by this new value. This has its uses. In fact, let's look at all the uses for the stack now. A very simple way of effectively using the stack is to hold temporary values when you have a lot of data to work with. Many older systems only had a handful of registers that you could actually perform math on, and if you needed more than that, you had to temporarily store your intermediate values back in memory somewhere. With the stack you just have to PUSH values you don't need yet, and POP them back later. No need to keep track of which memory addresses hold which temporary values. In this example, we added #$10 to the value in memory at $15DD, but preserved its original value. Of course, this only works if you are storing relatively few elements, or you have to plan in advance what order to PUSH your values, since you will only be able to retrieve them in reverse order. An extension of this idea is called register preservation. Say you have a value in a register that you need for later, but you need to make a subroutine call. If you want to be absolutely sure that you still have this value after the routine call, you should PUSH the value before the call, and POP it back afterwards. This way you still have the value you wanted even if the code in the subroutine clobbers the register in question. This is called Caller Preservation, since it's the caller of the subroutine that saves the registers. There is also the opposite case. If you are creating a subroutine and you want to make sure that any registers that the caller may want saved are preserved, you can PUSH them at the start of the routine and POP them back at the end. This is called Callee Preservation, since it's the the creator of the subroutine that saves the registers. Usually certain registers are designated to be caller preserved, and some callee preserved according to some convention. Now we move on to the biggest use of the stack--stack frames. Most assembly instruction sets have some sort of routine CALL and RETURN operations. This allows a chunk of code, a function, routine, subroutine, to be run multiple times from multiple places. This way the code doesn't have to duplicated over and over every time you need to use it. Now the CALL instruction encodes information about where the subroutine is located in memory (in this example, $AC19), but how does the RETURN instruction know where to return to? It can't encode this information itself, since the subroutine can be called from multiple places in the code. The answer is that the CALL operation will PUSH the location of the next instruction to the stack, so that the RETURN instruction can then POP it back off and jump to that location. So in this example, the address $ADBA is PUSHed to the stack, which is the location of the instruction directly after the CALL instruction. The bytes that hold the return addresses are the backbones of the stack frames. Each stack frame will contain its return address and other data elements that got PUSHed to the stack during that frame's execution. When a subroutine is called, the current frame is suspended, and a new frame is created on top of it. Nesting many subroutines within one another will result in more stack frames, and therefore a taller stack. One example where stack frames help structure things better is parameter passing. A parameter or argument is just a value that a subroutine needs in order to function properly. If the subroutine parameters are PUSHed to the stack before the routine is called, it can then expect to find these parameters in the frame directly underneath the current frame. In this example, the parameters #$31 and #$02 are in the stack underneath the routine's return address. Remember, the stack is all located in memory, so any element can be accessed at any time. In order for the subroutine to access its parameters properly during any point in execution, the memory should be accessed relative to the stack pointer. This ensures that no matter what the size of the stack is at the time of the function call, the parameters will still be located correctly. These parameter values can also be modified while they are in the stack, to create what are called out parameters. This is useful if the routine needs to output a bunch of values at once, and can't store them all in registers at the same time. There is one final thing to mention about stacks, and that is what happens when the value of the stack pointer goes outside its intended range. Remember that the stack pointer just points to somewhere in memory that marks the boundary between free memory and values in the stack. If it is known that the code being executed isn't going to make the stack grow very high, only a small portion of memory may actually be allocated to the stack. The stack pointer should never point anywhere outside of this specified range; if it does, there are going to be problems. When the stack becomes larger than its maximum intended size, that is, a value is PUSHed to the stack when it is completely full, this is called a stack overflow. If there is anything stored in the memory adjacent to the stack, it will be clobbered by the value that just got PUSHed. This can also occur by nesting too many subroutine CALLs--the return addresses that get PUSHed to the stack will be the culprit this time. Similarly, if the stack size becomes negative, that is, a value is POPed from the stack when it is completely empty, this is called a stack underflow. This is just as problematic, as the memory adjacent to the stack will be clobbered upon the next PUSH operation. This can also occur when the RETURN operation is executed, since it expects a return address to be POPed off of the stack. If you would like to support the channel, RGME is on Patreon at patreon.com/rgmechex. If you can't support, don't worry! Just subscribing on YouTube or even sharing the channel with others is more than enough. Actually, watching until the very end is even better yet! Thank you for watching.
Info
Channel: Retro Game Mechanics Explained
Views: 79,987
Rating: 4.9519854 out of 5
Keywords: video, game, programming, code, glitch, trick, explain, description, hack, tas, computing, stack, memory
Id: IWQ74f2ot7E
Channel Id: undefined
Length: 10min 57sec (657 seconds)
Published: Mon May 01 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.