| Memory and the End Game |
|
Page 1 of 4 In this series, I’ll take a stab at explaining the different types of objects used in every day applications, how they are held in memory, and how they are eventually cleaned up. Along the way, I’ll hopefully clear up some of the haze that generally surrounds the stack, the heap, object finalization, memory leaks, “unmanaged resources,” the GC (garbage collector), and some other things. This is Part 1 of 4, covering managed and unmanaged code, and the stack. Part 2 will discuss the heap. Part 3 will cover boxing and unboxing, stucts and classes, nondeterministic finalization, IDisposable, and garbage collection. Part 4 will discuss buffer overruns, stack overflows, how to hack and Xbox, and a few best practices for memory-management-aware programming in .Net. Managed Code, Unmanaged Code, and Managed WrappersManaged code is any code that is completely under the purview of the .Net Framework. It is executed with the framework by its side, and its life can be controlled by the framework. The framework knows what it’s doing at all times, and can even stop its execution and inspect its current state if necessary (for debugging, for example). It cannot live and is not executable without the .Net Framework. Unmanaged code is not aware of the framework, doesn’t need the framework, and can’t really be affected by the framework either. It is code that is simply loaded into memory and set to execute. It’s also code that was around before the framework was invented – for example, the WinAPI, which is the pre-Vista (mostly) foundation for Windows GUI development and consist of sometimes-seen-but-often-misunderstood executables like kernel32.dll, user32.dll, and gdi32.dll. WinAPI code, therefore, is unmanaged. A managed wrapper is managed code that uses, manages, and appropriately disposes of unmanaged code. Many of the classes in the BCL (Base Class Library) such as Image, Brush, Font, TextWriter, Stream, are managed wrappers. Many but not all are in the System.Drawing namespace because they use the GDI (gdi32.dll). Managed wrappers implement the IDisposable interface, which provides an implementation of a Dispose() method. The Dispose() method is responsible for cleaning up and freeing all the unmanaged code the object used while it was alive. It can be called by the developer to tidy up on demand, or it can be called by the GC (Garbage Collector) when it actually gets around to disposing of the object. The StackThe stack (or call stack or execution stack) is a fundamental data structure used by a thread to keep track of where it is in the process of executing code. Think of a stack of plates: You can add a new plate (a piece of information) to the stack by placing it on the top (“push” it on the stack), and then later you can remove a plate from the top (“pop” it off the stack). So let’s say you’re a computer and you’re asked to execute this code: 1 : main() On line 1 you enter the program, so you grab a plate from the cupboard, write “main()” on it so you know where you are, and place it carefully onto the counter to form a stack. Then you hit a for loop where you’re declaring a variable called “x”. So you grab another plate and write “x=1” and place that on the stack*. Then you get to a WriteLine() call, but that WriteLine() needs to know the value of x, and also where to return control when it’s done executing. So you grab another plate, write “WriteLine( 1 ); return to Line 6”, toss that onto the stack, and transfer control over to the memory location where the WriteLine() method is stored. WriteLine() does what it needs to do because it can look at the top plate on the stack and see that it’s getting the number one as an input parameter. Just for kicks, let’s say it’s had a bad day and wants to screw with you a bit. So after it faithfully prints out the “1”, it maliciously scratches out the number one on the plate, and changes it to a 42. Then it gets back to business. It notes the return location (Line 6) and puts the plate back in the dishwasher (“pops” it off the stack), then transfers execution back to the return location noted on the plate. It really doesn’t matter that a number was changed from one to 42 – that plate is already back in the dishwasher. (This is why changing local value member variables doesn’t affect anything outside the scope in which they're used.) Now back on Line 6, you realize you’re at the end of a for loop iteration, so you jump back up to line 3. The plate on the top of your stack says “x=1” so you scratch out the one, write in a two (as instructed by the “x++”), and check to see if the value you just wrote onto the plate is less than or equal to five. It is, so you go back into the loop and do the same thing you did a couple paragraphs ago, except the number you write on the new plate is two instead of one. You do that a few more times, filling up the dishwasher, until x = 6. At this point you’re done with your for loop, so you take the plate on top that says “x=6”, toss it over your shoulder, and move merrily along. This leaves you with a one-plate stack again. When you get to line 7, the plate you add is going to say, “WriteLine( ‘Done‘ ); return to Line 8”. You’re still going to call the same WriteLine() code as before, but this time you’re returning to a different location (and passing in a different value). This illustrates how the stack helps the computer remember where it is in the execution of the program. At Line 8, you realize that you’re at the end of a function, so you look at the plate on the top of your stack. It says “main()” but there’s no place to return execution, so you fling the last plate into the fireplace, shouting “Opa!” in your best Greek accent, and you’re done. No more work to do; no more dishes to dirty. Your thread no longer needs a slice of the processor’s time. * More specifically, the variables local to a specific code block are stored in the same stack frame (plate) as that block, but in this simple example, it’s just easier to think of it as just another plate on the stack. |
|||||||

