Hey, everyone. Welcome. We actually have some handouts for you today. We just decided to hand them out after you all sat down. So youfll be getting three handouts and they should be posted to the website, as my TA Dan Wilson is gonna do that sometime before 11:00 a.m. -- before noon. Well letfs see, last time, I had just given you a little bit of an introduction as to how the heap is managed by software thatfs included in C libraries that are linked against all of your own code. Every time something like six degrees, or RSG or vector test, or whatever, is created -- to remind you from where we were last time, wefre gonna start talking about all the various segments in memory. Each application behaves as if it owns all of memory. Wefll give more insight into that in a few lectures. Ifm specifically interested in the segment that, in practice, is usually -- I mean very approximately, but usually right around here. When an application is loaded into memory, the lowest address and the highest address of the heap are advertised to a library of code thatfs responsible for implementing malloc, free and realloc. Okay? Now this is a general sandbox of bytes. Malloc just hands out addresses to the internals. It actually records, behind the scenes, how big each figure actually is, so that when free is called and passed one of those three pointers there, it knows exactly how much memory to donate back to the heap. Ifll explain how that works, in a second. Because this is managed by software, malloc, free and realloc, the person implementing that can use whatever heuristics they want to make it run as quickly and efficiently and, obviously, as correctly as possible. And so wefre gonna talk about some of those heuristics and how that works. Now youfve always been under the impression that, when you do this -- Ifll just call it ARR is equal to malloc -- and you do something like 40 times the size of, oops, the size of int. And actually, Ifll go ahead and strongly type this pointer, to not be a void star but to be an int. Youfre under the impression that you get actually 16o bytes back. I can tell you, right now, that you do not -- you certainly have more than 160 bytes set aside on your behalf. If this is the heap -- I wonft draw all the other nodes that have been handed out, but at some point, it discovers a node that is large enough to accommodate that request right there. We think of it as perfectly sized to be 160 bytes. This address is ostensibly handed back to you and when you pass this either to realloc or to free, it actually can confirm internally -- or no, Ifm sorry, it canft confirm, but it just assumes that the pointer thatfs handed to free or realloc is one that has been previously handed back by either a call to malloc or realloc. Okay? So that is2 some legitimate pointer that has been handed back. The way memory is set aside on your behalf is that it actually allocates more than that number. Okay? For a variety of reasons. But what it will normally do before it hands back an address to you, if you ask for 160 bytes, itfll usually set aside 164 bytes or 168 bytes. Why? Because itfll actually include space at the beginning of either 4 or 8, or actually 16 or 32, whatever it decides, a little header on the full mode, where it can actually lay down a little bit of information about how big the node is. Does that make sense to people? So if this really is 160 bytes and this is 4, it might, among other things, write down a 164 inside that 4-byte figure. Okay? If itfs 8 bytes, it can actually write more than just the size. When you get a pointer back, you actually donft get a pointer to the head of the entire node; you get a pointer thatfs 4 or 8 bytes inset from the beginning. Does that make sense? You have access, seemingly, to all of that space right there. When you pass the pointer back to free -- letfs forget about realloc for the minute -- for a moment -- free says, gOh, Ifm just assuming that this a pointer that I handed back earlier. If I really handed this back, then I know that I put down, as a little footprint, how big this node was. So Ifm gonna cast this pointer to be an int star or a long star, or a long long star, or whatever it wants to. So that I can gracefully back up 4 or 8 bytes, interpret those 4 or 8 bytes, in a way that I know I laid down information before.h And say, gOh look, therefs a number 164.h That means, from this point to that point right there, should somehow be threaded back into the heap. Does that make sense to people? Okay. Given that right there, here are a couple of problems that I want you to understand why they donft work. Array is equal to malloc 100 times the size of int. So this is a legitimately allocated block of 100 arrays. You know that youfre gonna get either 104 bytes or 108 bytes, just a little bit more -- Ifm sorry 404 or 408 -- and then, you go ahead and you populate the array and you realize that you donft need all the space. Maybe you only need to use 60 of the integers and youfve recorded that in some other variable. Okay? And so you think youfre being a good memory citizen and you do this: ARR plus 60. Now the code wouldnft get written that way, it would probably be framed in terms of some local variable, say gnum ints in useh or something like that, or geffective length,h and you might because youfre more sensitive to memory allocation and you might want to donate back what youfre not using, might think that that should work. Well, if this is the 400 byte figure that youfve logically gotten, and youfve been handed that address and therefs a pre-node header of meaningful information there, meaningful to malloc and realloc, and you go ahead and hand back that address to free, depending on the implementation it may be very naive and just assume, without error-checking, that it was something that was handed back via malloc or realloc before. It might do some internal checking, but malloc and free realloc are supposed to be implemented as quickly as possible and not do the error checking for you because theyfre assuming that youfre really good at this C and C+ + programming thing, So theyfre not gonna interfere with execution by doing all of this error checking every single time free and malloc get called. So if you were to do this, right here, and it doesnft do any integrity checks on the number that it gets, it will blindly back up 4 or 8 bytes, whatever happens to reside in the two integers, or the one integer at index 58 and 59, would be interpreted as one of these things right here. And if it happens to store in the place where 164 is stored, right there, if it happens to store the number 29,000, it will go from this point forward 29,000 bytes and just blindly follow whatever algorithm it does to integrate a 29,000 byte block back into the heap. Okay? Does that make sense? Now youfre not gonna say, gSure, what impact that has on the heap and what data structures look like.h Ifll give you a sense in a second. But the bottom line, the takeaway point here, is that you canft do that and now you have some insight as to why. Okay? If you do this, int array 100, and you statically allocate your array, and you donft involve the heap at all, and you use it and because youfre new to C programming and you think that you have to free the array, if it doesnft do any error-checking on the address, itfs not even obligated to do any error-checking to confirm that itfs in the heap segment in the first place, it would go to the base address of your static array, back up 4 or 8 bytes, whatever figure and bit pattern happens to reside there would be interpreted as the size of some node, okay, that should be incorporated into the free list data structure. Okay, Ifm sorry, I shouldnft say free list because you donft know what that is yet. Incorporated into the collection of free nodes that the heap can consider for future -- in response to future call to malloc and realloc. Okay? Is this sitting well with everybody? Okay? The best implementations -- or well, best is up for debate -- but the fastest implementation is: Donft do error checking. Theyfll use heuristics to make sure they run as quickly as possible. Some implementations, Ifve never seen one, but Ifve read that some implementations do actually basically keep track of a set of void stars that have been handed back. And itfll do a very quick check of the void star, that it gets passed to it, against -- and make sure itfs a member of the set of void stars that have been handed out. And if itfs in debug mode, it might give an error if itfs not present. If itfs not in debug mode, it may ignore it and say, gIfm not going to free this thing because it will only cause problems later on.h Okay? Does that make sense to people? Yes, no? Got a nod. Okay. Now, as far as this 160 is concerned, that is not exactly a -- thatfs not exactly a perfect power of 2. Implementations that Ifve seen, if this is the entire heap, Ifve seen this as a heuristic. When you pass in a numbytes figure to malloc, letfs say that itfs 6, if you quickly recognize whether or not the number thatfs supplied is less than, say, 2 to the 3rd or 2 to the 5th, or 2 to the 7th, basically categorize and throw it in a size bucket as to whether itfs small, medium or large. Ifve seen some implementations actually divide the heap up, so that anything less than or equal to, say 2 to the 3rd equal to 8 bytes, is allocated from right there. Anything less than or equal to 2 to the 3rd, Ifm sorry, 2 to the, like, 6th equal to 64, might be allocated from this segment. And it would always give out a block that is, in fact, exactly 64 bytes or 8 bytes long. Okay? In other words, it wonft try to actually perfectly size everything because that takes a lot of work. If it can take some normative approach as to how it clips off individual segments within each sub segment, it might actually have a much easier time keeping everything clean and organized. Okay? As long as it allocates -- if you ask for 160 bytes, if it goes ahead and allocates 192 bytes, or 256 bytes, you actually donft mind all that much because at least youfre given -- youfre certainly given enough memory to meet the 160 request. Okay? Does that make sense? Yeah? [Student:] That doesnft necessarily mean that, if you have an array of 160 bytes, you [inaudible] Right, youfre not supposed to rely on implementation details because the implementation of malloc, free, and realloc on, say, you know, one flavor of Linux may actually be different than it is on another flavor of Linux. I mean, probably not. Ifd say all of those compilers are probably GNU backed, so itfs like GCC. But like, for instance, Code Warrior vs. X code on the Macintosh. They are implemented mostly, I mean primarily, by two different sets of engineers. And one may use one different heuristic for how to allocate stuff and those who wrote X code may have gone with a different approach. So you certainly canft assume you have that memory. Youfre just supposed to assume that youfve got the 160 bytes and that was it. Okay? Do you understand, now, a little bit why running over the boundaries of an array can cause problems? Sometimes, it doesnft. The most common overrun, actually, is at the end of the array. So youfll do something like i less than or equal to 10 as opposed to i less than 10. Youfll write one space too far. Consider the less common but certainly not unrealistic situation where you actually visit all your elements from top to bottom, you go one element too far, and you actually access and write to array of negative 1. You know where that resides. It happens to overlay the space where malloc and realloc actually put down information about how big the node is. So if you, for whatever reason, go and zero out from 100 down through zero, well then you actually go 1 too far then you zero out the 1 byte -- the 4 bytes where malloc is really relying on meaningful information to be preserved. And it will completely toy with the implementation of malloc and realloc in its ability to do the job properly. Okay? Does that make sense? Okay. What else did I want to talk about? As far as how it keeps track of all of the different portions within the heap, that are available to be handed back to the client, in response to malloc, realloc calls, let me go with this picture. Letfs assume that this is the entire heap and Ifm writing it to emphasize that itfs a stream of bytes. And a snapshot of any one moment -- this is allocated out, right here, and this is allocated out, and letfs say that this is allocated out, as well. Each of these will probably have a pre-node header at the front. Okay? With meaningful information -- they can actually keep more than just the size of the node. In fact, some implementations -- I have seen this, will not only keep track of how big this node is, but itfll actually also keep track of whether or not the node after it is free or not. So it can kind of simplify or optimize the implementation of realloc. So it can keep a pointer to this right here. Does that make sense to people? Okay? And it can go right here and see what -- how big it is and whether or not it can accommodate the stretch that is an option in response to a call to realloc. As far as all these blank nodes are concerned, it wants to be able to quickly scan the heap for unused blocks whenever malloc and realloc are called. What will typically happen is that it will interpret these nodes right here as variably sized nodes. Youfre familiar with that from Assignment Two and itfll overlay a link list of blobs over these 3 ravens right here. So the beginning of the heap is right there, right at the beginning, so it knows where the next pointer is. Itfll use the first 4 bytes of the unused blob to store the address of the next blob. Okay? Itfll store the address of the next blob right there, and then maybe, itfll put null there or maybe itfll cycle back to the front and use something of a circular link list approach. Every single time you call malloc or free, it obviously wants to traverse this link list and come up with some node thatfs big enough to meet the request. More often than not, I see a heuristic in place that just, basically, selects the first node that it can find that actually meets the allocation request. So if this type of thing isnft in use and itfs not segmented down into sub segments, and itfs just one big heap, it might start here. And if itfs just looking for a figure big enough to accommodate 8 bytes, maybe this one will work. Okay? If it needs 64 bytes and this is only 32, but this is 128, it will say, gYoufre not big enough, but you are.h Does that make sense? So it would approach this first fit heuristic in searching from the beginning. There are other heuristics that are in place. Sometimes, they do aggressively search the entire heaps free list. Thatfs what this thing is called. Thatfs what I used earlier. And itfll actually do an exhaustive search because it wants to find the best fit. It wants to find the node that is closest in size to the actual size thatfs needed by the call to malloc so that as little as memory as possible is left over in some free node. Does that make sense? Okay? Ifve seen -- Ifve read about, although Ifve never seen, that sometimes they use a worst fit strategy. Which means they basically scan the entire heap for the biggest node and use that one with the idea that the part thatfs left over will be still fairly big. And wefre not likely to get little clips of 4 and 8 bytes which are gonna be more or less useless for most malloc calls. Does that make sense? I have seen heuristics where they actually remember where they left off, at the end of malloc. And the next call to malloc or realloc -- Ifm sorry, the next call to malloc will actually continue from that point. So that, all parts of the heap are equally visited during an executable that runs for more than a few seconds. Okay? So you donft actually get lots of complexity over here and then this unused heap over here. Okay? Does that sit well with everybody? There are all types of things that can be done here. Therefs extra meta-information can be stored here, and there, and there, about what comes afterwards, so it can simplify the implementation of free and realloc. If I go ahead and free this node right here, when itfs actually freed and donated back to the heap, none of this is changed, but the first 4 bytes actually set to thread to that right there. And this right here would be updated to point to there instead. Does that make sense to people? Okay? It wouldnft actually go out and clear any information because it just doesnft want to bother doing that. It could, but itfs just time consuming. And this could, in theory, be 1 megabyte and why go through and zero out 1 megabyte of information, when the clientfs not supposed to touch it again? Okay? Let me just erase this, not because itfs would be cleared out, but just so it looks like a free node, like all the other ones. Some implementations would actually prefer not two so-and-so size nodes together but one big node, since they canft see many disadvantages to having one very large node vs. two side-by-side smaller nodes. Does that make sense? So some of them will go to the effort of actually coalescing nodes so that the free list is simpler and they have a little bit more flexibility as to how they chop things up. Okay? Make sense? I have seen, and this is kind of funny, I have seen some implementations of free actually just record the address as something that should be freed, ultimately. But it actually doesnft commit to the free call until the very next malloc call or, in some cases, until it actually does need to start donating memory back to the free list because it canft otherwise meet the request of malloc and free alloc -- malloc and realloc. Does that make sense? So Ifm speaking in, like, run-on paragraph form here about all the things that can be done. Thatfs because itfs written in software and people, those that design these things are typically very good systems programmers. They can adopt whatever heuristic they want to, to make the thing run, certainly correctly but as efficiently and elegantly as possible. Okay? And if, ultimately, this all starts out as one big random blob of bytes, the way the heap is set up is that the address of the entire free list is right there and the first 4 bytes have a null inside of it. Okay? And that basically means that therefs no node following this one. It would also probably have some information about how big the entire heap is. Okay? Because thatfs just the type of information thatfs maintained on behalf of all nodes at all times, so it just happens to be the size of the heap when things start out. Okay? Now, consider this problem right here. Herefs the heap again and letfs say the entire thing is 200 bytes wide. This is free and itfs 40 bytes. This is allocated, and letfs say it is 20 bytes, not drawn to scale. Letfs say that this is 100 bytes wide and it is free. Letfs say that this is in use; it is 40 bytes wide. And is that gonna work out? No, letfs make this a little bit smaller. Letfs make this 80; make this 20 and so this, right here, is unused. And of course, the heap is much bigger than this and the way itfs been chopped down in used block and unused blocks, is a little bit more complicated than this. But you certainly understand what I mean, when I say that there are 160 free bytes in my heap of, thatfs normally of size 200. Okay? If I go ahead and make a malloc request for 40, it could use this because itfs the best fit or the first fit. It could use this because itfs the worst fit. Okay? It can use whatever it wants to as long as it gets the job done and youfre not blocked by a faulty implementation. If I ask for 100 bytes, itfs not gonna work out, right? Because, even though the sum of these free nodes is a whopping 160 bytes, theyfre not uniformly aggregated in a way that you need when you malloc 100 bytes or 160 bytes. Okay? I mean, you really have to assume these things are gonna be used as an array of ints or structs, or whatever. And you canft expect the client to really absorb a collection of pointers and figure out how to maintain an array or overlay an array over multiple blocks. So you may ask yourself, gWell, can I actually slide this over and slide this over even further, to come up with this type of scenario where this at the front and this is right next to it? And then I really do get 160 bytes that are free -- 20 and 20, so that if I ask for 100 bytes, I can actually use it?h This process, it does exist. It wonft exist in this problem for -- in this scenario for reasons Ifll explain in a second. But what Ifm really doing here is Ifm just basically compacting the heap like a trash compactor compacts trash. Okay? Try and get it as close to the front as possible, okay because you know that, what remains after everythingfs been sifted to the front, is one very large block. Does that make sense? Okay? Thatfs all fine and dandy except for the fact that you, very likely, Ifm sure itfs the case, have handed that address and that address out to the client. And itfs going to resent it if move the data out of its way. Okay? For you to bring these over here means that the client is still pointing there and right there and now, they have no idea where their data went. Okay? Thatfs a problematic implementation. So there are some benefits of actually compacting this heap. Okay? Now I havenft seen any modern systems do this, but I know that the Macintosh did this about 12 years ago, when I was doing more systems work on the Macintosh. Oops, you know thatfs fun. Recognizing that there are some advantages to being able to compact the heap to create fewer large nodes as opposed to lots and little fragmented nodes, they might clip off some part of the heap. This is used in a traditional manner by the heap manager. Itfs directly dealt with by malloc, free and realloc. But they might clip this part off and, even though it looks small on the board, it would in theory be a pretty large fraction of the entire heap, which is, you know, megabytes or -- megabytes or gigabytes in size. It would manage this via handles. So rather than handing out a direct pointer into the heap, like we have up there, and we saw that interfered with heap compaction, what some operating systems did, in addition to malloc and free and realloc, would actually hand out what are called handles, which are not single pointers directly to data, but pointers that are two hops away as opposed to one hop away from the data. And you may say, gWell, why would they want to do that?h Well, they would not only hand out double pointers, but they would actually maintain a list of single pointers. If you ask for 80 bytes via a handle as opposed to a pointer, then it could maintain a table of master pointers and hand out the address of that to the client. Okay? So the client is now two hops away from his data, but the advantage is that this is owned by the heap manager and, if it wants to do compaction on this upper fourth of the picture, it can do it because it can also update these without affecting your void double stars. Does that make sense? Okay? I saw that used in Mac OS 7.6 and Mac OS, like 8 -- not actually 8 never existed, but like all of the 7 series of Macintosh Operating System, probably from 1994 and f95, when I was doing some consulting work that involved this. Very clever idea, the problem is that thing is compacted in the background, at a low-priority thread. Okay? Or it becomes higher priority if therefs a demand for this and therefs no space for it. You canft actually have the heap compaction going on simultaneously to an actual double d reference request because you canft actually have data sliding in the moment while youfre trying to access it. So the paradigm, or the idiom rather, that people would use for this is, if you really wanted to get memory to something thatfs compacted and managed more aggressively by the heap manager, you would do something like this. Void star star handle and it would be some function like new handle. Thatfs what it was in Mac OS 7.6 days. You might ask for 40 bytes. Okay? Recognizing that the 40 bytes might be sliding around in a low-priority thread in the background, to keep that thing as compact as possible, you wouldnft bother with it when you knew that you were gonna certainly read to but even read from those 40 bytes. That you actually, somehow, had to tell the operating system to stop moving things around, just long enough for me to read and/or write. Okay? So you would do something like this, handle lock, which basically puts safety pins on all the blocks in that upper fourth of the diagram and you say, gYou can move everything else around, but you better not change the pointer thatfs maintained in this table, thatfs addressed by my handle. Because Ifm gonna be annoyed if you do because Ifm about to manipulate it right here.h And if youfre a good programmer, when youfre done, you unlock, you call handle unlock, so that flexibility has been restored for the heap manager to start moving things around, including the block thatfs addressed right there. Does that make sense? So therefs a hint of, like, concurrency there. Wefll talk about that more in a few weeks. But thatfs how heap compaction, as an idea, can be supported by a heap manager, okay, without interfering with your ability to actually get to the data. Okay? You never really lose track of it because youfre always two hops away as opposed to one hop away. Okay? Does that make sense? Okay, very good. Ifm trying to think what else. I think thatfs enough. Just kind of a hodgepodge of ideas. Ifm not gonna have you implement any of this stuff, okay? But wefve actually -- Ifll try and dig up a section problem, not for this week but for the week after, where people on the mid-term implemented a little miniature version of malloc, where you actually had to understand all of this stuff. Okay, now I can tell you right now that Ifm not gonna do that because that problem didnft go very well, when I saw it was given. But itfs certainly a suitable section problem and something that you can do in like a less time-pressured environment. Okay? Make sense? Okay. Software managed. It is managed entirely by malloc, realloc and free. What I want to do now, is I want to start talking a little bit about the stack segment. Let me get some clear board space here. The first 20 minutes of this actually very easy to take. Itfs Monday thatfll be a little bit more intense. When I talk about the stacks thing, I drew it last time; itfs typically at a higher address space, as a segment. This is the entire, as a rectangle, the entire thing is set aside as a stack segment, but youfre not always using all of at any one moment. In fact, youfre using very little of it when a program begins because therefs so few active functions. Okay? Usually, the portion thatfs in use is roughly -- itfs very rough ?roughly proportional to the number of active functions, of the stack depth of all the functions that are currently executing. Okay? I will draw more elaborate pictures within this in a little bit. But let me just invent a function and I donft care about code so much as I just care about the local variable set. Letfs say that the main function -- letfs not worry about any local, any parameters -- letfs say that it declares an int called ga.h Letfs say that it declares a short array called gbh of length 4, and letfs say that it declares a double called gc.h Okay? Actually, I donft want to call this gmain.h Letfs keep it nice and simple; letfs just call it the ga function.h All of the implementation of gah does, is it calls gbh -- Ifm not concerned about passing parameters, and Ifd call gch afterwards, and then it returns. Wefll blow this off for a second. When you call the gah function, obviously space needs to be set aside for this int and those 4 shorts and that single double there. 1-byte figure, four 2-byte figures and one 8 byte figure. Not surprisingly, itfs somewhat organized in the way that it clips off memory. It doesnft take them from the heap; it actually draws the memory from the stack. All of the memory thatfs needed for these four variable right here -- Ifm sorry, three variables and technically, like six because therefs four of them right here, theyfre packed as aggressively as possible and therefs even some ordering scheme thatfs in place. Because gah is declared first, gah gets a 4-byte rectangle, and because gbfsh declaration is right afterwards, itfs allocated right below it in memory. Okay? This being an 8 byte double would have a rectangle thatfs twice as tall and Ifll just do that to emphasize the fact that itfs two [inaudible] as opposed to one. Okay? Does that make sense? When you call this gah function, what happens is, if I go ahead and just represent -- I canft -- I donft want to draw this entire thing again. So just let this little picture right here be abbreviated by the hexagon. Okay? This thing is whatfs called a activation record, or stack frame, okay, for this gah function. Letfs assume that gah is about to be called. When gah is called, this pointer, thatfs internal into the stack segment, it actually separates the space thatfs in use from the space thatfs not in use. Itfll actually decrement this by size of hexagon. Okay? Do you understand what I mean when I say that? And that means that the memory right here is interpreted according to this picture, right here. Itfs like this picture right here overlays those 20 bytes that the stack pointer was just decremented by. Okay? Notice it doesnft lose track of all the previously declared variables that are part of functions above gah in the stack trays. And when gah calls gb,h and calls gc,h itfll decrement the stack pointer even a little more. Does that make sense? Okay. Maybe element gbh -- Ifll keep these simple. Void gbh declares an int called gx,h a car star called gyh and letfs say a car star array called gzh of length 2. The stack frame would look like this. gXh would be above gyfsh 4 by pointer, and then, this is an array of size 2. So this is a total of 16 bytes, there. Ifll abbreviate it with a triangle. Okay? And letfs say that gbh actually calls gch as well and gch has a very simple local variable set, Ifll say a double m of length 3, and Ifll just do a stand-alone int called gmh and it doesnft do anything that involves any other function calls. The activation for this would be big, therefs three doubles; therefs a stand-alone int below it; therefs gmh -- this is all of gmh of zero through 2. And Ifm gonna abbreviate this with a circle. Okay? So the very first time gah gets called, you know just the work flow will bring you from gah to gbh to gch, back to gbh, back to gah, which will then call gch. Okay? So as far as the stack is concerned -- let me just draw a really narrow stack, okay, to emphasize the fact that the full width is being used to help accommodate the stack frames right here, when gah is called, whateverfs above here, we have no idea. Theyfre obviously gonna correspond to local variables; theyfre preserving their values; therefs just no easy way to access them, unless you happen to have pointers passing those parameters. Okay? When you call ga,h this thing is decremented by size of hexagon. Oops. I needed to remind myself what a hexagon looked like. Thatfs that and then, this pointer right here, is kind of the base or the entry point that grants the gah function access to all of its local variables. It knows that the last parameter gch is at an offset of zero from this pointer right here. Okay. 8 bytes above that is where the second to the last variable that was declared can be accessed, etc. This pointer is actually stored on the hardware; wefll see that on Monday. But this actually whatfs called the stack pointer and it always keeps track and points to the most recently called functions activation record. When gah calls gb,h it decrements this even further. Ifm gonna stop drawing the arrow and the triangle activation record is laid right below that. Okay? Access to this, it just isnft available to the gbh function because gbh doesnft even know about it. It just -- itfs incidental that it happens to be above it, but gbh can certainly access all the variables that part of the triangle activation record. Since gbh calls gc,h itfs gonna lay a circle below that. When it calls gc,h that circle will be alive and in use, the stack pointer will be pointing right there until gch is done. When gch exits, it simply raises the stack pointer back to where it was before gch was called. Whatever information has been written there, okay, it actually stays there. Okay? But itfs supposed to be out of view; it canft be accessed. Okay? So -- Ifm sorry, gch returns and then gbh returns and we come back to that situation and the drawing there technically corresponds to the moment -- wefve just returned from gbh but we have yet to call gc.h Right? And so when it calls gc,h it follows the same formula. It has no idea that gbh has been called recently. And the formula here is that it just overlays a circle over the space where the triangle was before. Okay? So it actually layers over whatever information was legally interpreted and owned by the gbh function. But it doesnft know that and itfs not really gonna be the case that gch has any idea how to interpret gbfsh data. It doesnft even necessarily know that gbh was called recently. All itfs really doing is itfs inheriting a random select -- a random set of bits that itfs supposed to be initialize to be meaningful if itfs going to do anything useful with them. Okay? Does that make sense? Do you now see why this thing is called a stack? Ifm assuming you do. Okay? Every time -- before you can actually access gbfsh variables, if you call gch -- gch has to be popped off the stack the stack frame, before you can come back to gb.h Okay? Now wefre gonna be a less shape-oriented in a little bit and start talking about assembly code and how it manipulates the stack. So wefre gonna suspend our discussion of the stack for, like half a lecture, okay? But now, I want to start focusing a little bit on assembly code. Okay? And how that actually guides execution. Wefre ultimately going to be hand compiling little snippets of C and C + + code to a mock assembly language. Nothing -- itfs not an industrial thing like MIPS or X86 or something like that. That would be more syntax than concepts. So we have this very simple syntax for emulating the ideas of an assembly language. And I want to start talking about that now. There is a segment down here. It usually doesnft have to be that big because the heap and the stack -- the stack is actually never this big when it starts out; it can be expanded. The heap is usually set aside to be very big because anything thatfs? any huge memory resources are typically dynamically allocated while the program is running. This right here, Ifm gonna refer to as the gcode segment.h Just like the heap and the stack, it stores a pattern of zeros and ones there, but all of the information in the code segment corresponds to the assembly code that compiled from your C and C + + programs. Does that make sense? Okay. So Ifll give you an idea of what these things look like in a little bit. Let me just talk about, at least at the cs107 level, what a computer processor looks like. So Ifve already drawn all of RAM a couple of times, Ifll do it one more time. There it is; I donft have to break it down, just stack and heap and code. Thatfs all of RAM. In any modern computer, therefs usually -- now itfs very often that therefs more than one of these things, but wefre just gonna assume a UNA processor, okay, where this is relatively slow memory. Youfre not used to hearing of RAM, which this is, as slow. But itfs slow compared to the memory thatfs set aside in the form of a register set. Ifm drawing it -- it makes it look like itfs one-fourth the size of RAM; itfs not; itfs much smaller. In our world, wefre gonna actually assume that all processors have 32 of these registers, where a register is just a general purpose 4 byte figure that we happen to have really, really fast access to. Okay? Does that make sense to people? Ifm going to call this R 1; Ifm going to call this R 2; Ifm going to call this R 3 and Ifm going to draw a 3 instead of a 2, etc. And those are going to be the names for these registers. The registers, themselves, are electronically in touch with all of RAM. Therefs a reason for that. Okay? And when Ifm doing this, Ifm just drawing this big network of silicone wires that are laid down microscopically -- okay, not microscopically, but you know what I mean. So that, every single register can technically, draw and flush information to and from to and from RAM. Okay? The 16 or the 32 registers right here, are also electronically in touch with the electronics. Itfs always drawn this way; Ifm not really sure why. Whatfs called the garithmetic logic unith or the ALU, that is the piece of electronics thatfs responsible for emulating what we understand to be addition and multiplication, and left shifting and right shifting of bits, and masking it. All of the things that can be done very easily on 4 byte figures. Okay? Does that make sense to people? So we can support plus and minus, and times, and div, and mod and double less than and double greater than, and double ampersand and double vertical bar, and all of these things because of this ALU right here. Okay? It is electronically in touch with the register set. There are some architectures that use a slightly scheme right here. Ifm going with an architecture or an assembly code -- Ifm sorry, Ifm going with a processor architecture where the ALU is just in touch with these things right here, and itfs not directly in touch with general RAM. The implications of that is that all meaningful mathematical operations have to actually be done using registers. Does that make sense? Now you may think that thatfs kind of a liability, that youfd have to actually take something from memory, load it into a register, in order to 1 to it. Okay? Well, that actually is what happens. The alternative is for you to get this right here to electronically in touch with all of RAM and that would be either prohibitively expensive, or it would be prohibitively slow. Okay? So they can optimize on just load and store between registers and general RAM and then, once they get something into the register set, thatfs where they do all the interesting stuff. So the typical idiom that is followed for most statements -- anything mathematical -- think i plus plus, or i plus 10 or something like that, is to load the variables from wherever they are, either in the stack or the heap, okay; load them into registers; do the mathematics; put the result in some other register, and then flush the result thatfs stored in the register, out to where it really belongs in memory. Okay? Just assume, without committing to assembly code right here, that thatfs the space that happens to be set aside for your favorite variable, i. And it has a 7 inside of it. Okay? Letfs say that somewhere, probably close by, there is a 10. Okay? And youfre curious as to what happens on your behalf at the memory level in response to that right there. Well, truly, what happens is the j plus i compiles to assembly code and the assembly code is the recipe that knows how to load j and i into register set; do the addition and then flush the result back out to the same space that j occupies. Okay? But just in terms of actually seeing where -- how the 7 and 10 move around, what would probably happen is the 7 would be loaded into R 1. The 10 would be loaded into R 2. You can actually add the 10 and the 7 and store the result in R 3 because these two registers are in touch with the electronics that are capable of doing that addition for you. And after you synthesize the result right here, you can flush it back out to j. So given what Ifve shown you, so far, itfs not a useless metaphor for you to just think about assembly code instructions as byte shovelers, okay, to and from RAM, and also doing very atomic, simple mathematics. Okay? Plus, minus, things like that. Okay? The components of assembly code that really are different are usually implementation. I think the double e aspects of computer architecture are very difficult, probably because Ifve never really studied it; Ifm a compute scientist. Okay? But also, the parts that are interesting to us, is how something that is stated, usually quite clearly, in C or C + + code, actually gets translated to all of these assembly code instructions, such that the assembly code, which is in touch with memory, actually executes and imitates the functionality of the C and C + + code. Okay? If this is my C + + code, okay, then I need this to become a 17. How does the assembly code actually do that for me? How do I have this one to one translation or one to many translation, between individual C and C + + statements, okay, and the assembly code instructions that have to be done in sequence in order to emulate that, right there. Does that make sense? Now, given these pictures over here, you shouldnft be surprised that j doesnft move as a variable while that program -- while that statement is running. Okay? So itfs always gonna be associated with the same address in RAM. Okay? Thatfs good. You donft want it flying around when you try to write a 17 back to it. Okay? You may ask why I donft -- why they wouldnft -- what are the disadvantages of trying to get this to be in touch with general memory? I mean, if we have to have this in touch with that -- if we have to have this complex matrix between the register set and this right here, okay, then why not just have it this complex matrix of wires between general RAM and the ALU? It just makes the implementation of the hardware that much more complicated. Therefs no way of getting around at least a few set of operations between RAM and the register set. Okay? At the very least, you have to have load and store instructions to move four 2 and 1 byte quantities to and from RAM, in between the register set. Does that make sense? You have to have at least those. If you actually try to support addition between two arbitrary addresses, it can technically be done. It might make a clock cycle thatfs actually on the order of seconds. Okay? But it technically can be done. But hardware designers obviously want the hard -- want to be able to do any form of atomic operation at the hardware level, but they also want it to run as quickly as possible. So given that this right here would have to correspond to more than a few actions; load i into register; load j into a register; do the addition and write things back. There were, like basically four verbs in that sentence, four actions that needed to happen in order for that to emulate that right there. What Ifm loosely saying is this is a statement that will correspond or gen -- will -- this will compile to four assembly code instructions, two loads, an addition and a store. If it tried to optimize and do all of this in one assembly code instruction, it could do it. Okay? It would actually probably require that this be in touch with that right there, but the clock cycle would have to increase because whenever you have a more complicated hardware implementation, itfs generally the case that the clock cycle speed goes up. It may go up -- if it goes up by more than a factor of 4, then youfd actually prefer the really simple, load; do the work here and then flush out. Because in the end, youfre really worried about correctness but also speed. Okay? And if you have this very robust implementation, where everything can be done directly in memory, but the clock cycle is on the order of, like, milliseconds as opposed to nanoseconds -- or not nanoseconds, microseconds, okay, then you actually prefer to go with the simpler idea, this load store architecture, where you always load things into registers, manipulate them in a meaningful, mathematical way and then flush the result out to memory. Okay? So I actually donft have any time. I have 25 seconds; I canft do very much, then. What I will do on Monday is I will start seemingly inventing assembly code instructions. And I will come up with syntax for actually loading into a register a 4-byte figure from general memory, doing the opposite, taking something thatfs in a register and then flushing it out. And then, talking about what forms of addition and subtraction and multiplication are supported between two different registers. Okay? Wefll get ?