Stack N Heap
Stack N Heap
The University of
Basic Pointers
Pointers Before and After
There's a lot of nice, tidy code you can write without knowing about pointers. But once you learn to use the power of pointers, you can never go back. There are too many things that can only be done with pointers. But with increased power comes increased responsibility. Pointers allow new and more ugly types of bugs, and pointer bugs can crash in random ways which makes them more difficult to debug. Nonetheless, even with their problems, pointers are an irresistibly powerful programming construct.
What Is A Pointer?
Simple it fo t operate pretty intuitively. An int and variables n l a variable is like a box which can store a single it such as 42. In a drawing, a simple variable is a box with its current value value n drawn inside.
A pointer works a little differently it does not store a simple value directly. Instead, a pointer stores a reference to another value. The variable the pointer refers to is sometimes known as its "pointee". In a drawing, a pointer is a box which contains the beginning of an arrow which leads to its pointee. (There is no single, official, word for the concept of a pointee pointee is just the word used in these explanations.) The following drawing shows two variables: nm nThe simple variable num u the value and . u P contains m 42 in the usual way. The variable n a pointer which contains a u is reference to the variable nm .u The nm variable is the pointer and num pointee. What is stored inside of n Its value is not an int u is its ? P. m Its value is a reference to an int .
Computer Programming
Handout Lahore
The University of
Pointer Dereference
The "dereference" operation follows a pointer's reference to get the value of its pointee. The value of the dereference of nm above is 42. When the dereference operation is used u P correctly, it's simple. It just accesses the value of the pointee. The only restriction is that the pointer must have a pointee for the dereference to access. Almost all bugs in pointer code involve violating that one restriction. A pointer must be assigned a pointee before dereference operations will work.
The C++ language uses the symbol NULL for this purpose. NULL is equal to the integer constant 0, so NULL can play the role of a boolean false.
Pointer Assignment
The assignment operation (=between two pointers makes them point to the same pointee. ) It's a simple rule for a potentially complex situation, so it is worth repeating: assigning one pointer to another makes them point to the same thing. The example below adds a second pointer, sassigned with the statement scd result is that sc , en cd o .The en o points to the same = e o pointee as nmt the drawing, this means that the sc nm . ur In P and boxes both contain arrows pointing to num e u o P . Assignment between pointers does not change or even touch the pointees. It just changes which pointee a pointer refers to.
Computer Programming
Handout Lahore
The University of
After assignment, the = comparing the two pointers will return true. For example test = above =true. (en is d The assignment operation also works with the n so c = NULL value. An assignment operation with a NULL pointer copies the NULL value from one pointer to another.
Make A Drawing
Memory drawings are the key to thinking about pointer code. When you are looking at code, thinking about how it will use memory at run time....make a quick drawing to work out your ideas. This article certainly uses drawings to show how pointers work. That's the way to do it.
Sharing
Two pointers which both refer to a single pointee are said to be "sharing". That two or more entities can cooperatively share a single memory structure is a key advantage of pointers in all computer languages. Pointer manipulation is just technique sharing is often the real goal.
Computer Programming
Handout Lahore
The University of
Bad Pointers
When a pointer is first allocated, it does not have a pointee. The pointer is "uninitialized" or simply "bad". A dereference operation on a bad pointer is a serious runtime error. If you are lucky, the dereference operation will crash or halt immediately. If you are unlucky, the bad pointer dereference will corrupt a random area of memory, slightly altering the operation of the program so that it goes wrong some indefinite time later. Each pointer must be assigned a pointee before it can support dereference operations. Before that, the pointer is bad and must not be used. In our memory drawings, the bad pointer value is shown with an XXX value...
Bad pointers are very common. In fact, every pointer starts out with a bad value. Correct code overwrites the bad value with a correct reference to a pointee, and thereafter the pointer works fine. There is nothing automatic that gives a pointer a valid pointee. Quite the opposite most languages make it easy to omit this important step. You just have to program carefully. If your code is crashing, a bad pointer should be your first suspicion.
Two Levels
One way to think about pointer code is that operates at two levels pointer level and pointee level. The trick is that both levels need to be initialized and connected for things to work. (1) the pointer must be allocated, (1) the pointee must be allocated, and (3) the pointer must be assigned to point to the pointee. It's rare to forget step (1). But forget (2) or (3), and the whole thing will blow up at the first dereference. Remember to account for both levels make a memory drawing during your design to make sure it's right.
Syntax
The above basic features of pointers, pointees, dereferencing, and assigning are the only concepts you need to build pointer code. However, in order to talk about pointer code, we need to use a known syntax which is about as interesting as....a syntax. We will use the C++ language syntax.
Pointer Variables
Pointer variables are declared just like any other variable. The declaration gives the type and name of the new variable and reserves memory to hold its value. The declaration does not assign a pointee for the pointer the pointer starts out with a bad value.
Computer Programming
Handout Lahore 7
The University of
Computer Programming
Handout Lahore
The University of
Computer Programming
Handout Lahore
The University of
Allocating a pointer does not automatically assign it to refer to a pointee. Assigning the pointer to refer to a specific pointee is a separate operation, which is easy to forget. Assignment between two pointers makes them refer to the same pointee which introduces sharing.
Local Memory
Local variables are the programming structure everyone uses but no one thinks about. You think about them a little when first mastering the syntax. But after a few weeks, the variables are so automatic that you soon forget to think about how they work. This situation is a credit to modern programming languages most of the time variables appear automatically when you need them, and they disappear automatically when you are finished. For basic programming, this is a fine situation. However, for advanced programming, it's going to be useful to have an idea of how variables work...
Local Memory
The most common variables you use are "local" variables within functions such as the variables nm rs the following function. All of the local variables and parameters taken and inu u e together are called its "local storage" or just its "locals", such as nm rs the following and in u e code... The variables are called "local" to capture the idea that their lifetime is tied to the function where they are declared. Whenever the function runs, its local variables are allocated. When the function exits, its locals are deallocated. For the above example, that means that when the Square() function is called, local storage is allocated for nm rs and . el u u t Statements like result= num * use the local storage. When the function finally exits, its in the function num ; local storage is deallocated. Here is a more detailed version of the rules of local storage... Sobia Tariq Javed
Computer Programming
Handout Lahore
The University of
1. When a function is called, memory is allocated for all of its locals. In other words, when the flow of control hits the starting '{' for the function, all of its locals are allocated memory. Parameters such as nm local variables such as rsl above and u inuthe et example both count as locals. The only difference between parameters and local variables is that parameters start out with a value copied from the caller while local variables start with random initial values. This article mostly uses simple it variables for its examples, however local allocation works for any type: structs, n arrays...these can all be allocated locally. 2. The memory for the locals continues to be allocated so long as the thread of control is within the owning function. Locals continue to exist even if the function temporarily passes off the thread of control by calling another function. The locals exist undisturbed through all of this. 3. Finally, when the function finishes and exits, its locals are deallocated. This makes sense in a way suppose the locals were somehow to continue to exist how could the code even refer to them? The names like nm rsl t and only make sense u e u within the body of Square() anyway. Once the flow of control leaves that body, there is no way to refer to the locals even if they were allocated. That locals are available ("scoped") only within their owning function is known as "lexical scoping" and pretty much all languages do it that way now. Small Locals Example Here is a simple example of the lifetime of local storage...
Computer Programming
Handout Lahore
The University of
Advantages Of Locals
Locals are great for 90% of a program's memory needs.... Convenient. Locals satisfy a convenient need functions often need some temporary memory which exists only during the function's computation. Local variables conveniently provide this sort of temporary, independent memory. Efficient. Relative to other memory use techniques, locals are very efficient. Allocating and deallocating them is time efficient (fast) and they are space efficient in the way they use and recycle memory. Local Copies. Local parameters are basically local copies of the information from the caller. This is also known as "pass by value." Parameters are local variables which are initialized with an assignment (=operation from the caller. The caller is ) not "sharing" the parameter value with the callee in the pointer sense the callee is getting its own copy. This has the advantage that the callee can change its local copy without affecting the caller. (Such as with the "p" parameter in the above example.) This independence is good since it keeps the operation of the caller and callee functions separate which follows the rules of good software engineering keep separate components as independent as possible.
Disadvantages Of Locals
There are two disadvantages of Locals Short Lifetime. Their allocation and deallocation schedule (their "lifetime") is very strict. Sometimes a program needs memory which continues to be allocated even after the function which originally allocated it has exited. Local variables will not work since they are deallocated automatically when their owning function exits. This problem will be solved later in Section 4 with "heap" memory. Restricted Communication. Since locals are copies of the caller parameters, they do not provide a means of communication from the callee back to the caller. This is the downside of the "independence" advantage. Also, sometimes making copies of a value is undesirable for other reasons.
Computer Programming
Handout Lahore
The University of
are also sometimes known as "stack" variables because, at a low level, languages almost always implement local variables using a stack structure in memory.
Computer Programming
Handout Lahore
The University of
5. When foo() is finished, it exits by popping its locals off the stack and "returns" to the caller using the previously stored return address. Now the caller's locals are on the end of the stack and it can resume executing. For the extremely curious, here are other miscellaneous notes on the function call process... This is why infinite recursion results in a "Stack Overflow Error" the code keeps calling and calling resulting in steps (1) (2) (3), (1) (2) (3), but never a step (4)....eventually the call stack runs out of memory. This is why local variables have random initial values step (2) just pushes the whole local block in one operation. Each local gets its own area of memory, but the memory will contain whatever the most recent tenant left there. To clear all of the local block for each function call would be too time expensive. The "local block" is also known as the function's "activation record" or "stack frame". The entire block can be pushed onto the stack (step 2), in a single CPU operation it is a very fast operation. For a multithreaded environment, each thread gets its own call stack instead of just having single, global call stack. For performance reasons, some languages pass some parameters through registers and others through the stack, so the overall process is complex. However, the apparent the lifetime of the variables will always follow the "stack" model presented here.
Reference Parameters
In the simplest "pass by value" or "value parameter" scheme, each function has separate, local memory and parameters are copied from the caller to the callee at the moment of the function call. But what about the other direction? How can the callee communicate back to its caller? Using a "return" at the end of the callee to copy a result back to the caller works for simple cases, but does not work well for all situations. Also, sometimes copying values back and forth is undesirable. "Pass by reference" parameters solve all of these problems. For the following discussion, the term "value of interest" will be a value that the caller and callee wish to communicate between each other. A reference parameter passes a pointer to the value of interest instead of a copy of the value of interest. This technique uses the sharing property of pointers so that the caller and callee can share the value of interest.
Computer Programming
Handout Lahore
The University of
B() adds 1 to its local wr but when B() exits, wrh copy, o t is t odeallocated, so changing it was useless. The value of interest, nrests unchanged the whole time in A()'s local storage. A function , eo tr W can change its local copy of the value of interest, but that change is not reflected back in the original value. This is really just the old "independence" property of local storage, but in this case it is not what is wanted.
The reference parameter strategy: B() receives a pointer to the value of interest instead of a copy.
Computer Programming
Handout Lahore
The University of
Passing By Reference
Here are the steps to use in the code to use the pass-by-reference strategy... Have a single copy of the value of interest. The single "master" copy. Pass pointers to that value to any function which wants to see or change the value. Functions can dereference their pointer to see or change the value of interest. Functions must remember that they do not have their own local copies. If they dereference their pointer and change the value, they really are changing the master value. If a function wants a local copy to change safely, the function must explicitly allocate and initialize such a local copy.
Syntax
The syntax for by reference parameters through pointers in the C++ language just uses pointer operations on the parameters... 1. Suppose a function wants to communicate about some value of interest or ora r itfott n lsu . f 2. The function takes as its parameter a pointer to the value of interest an or* l . t itfosr n ora u f Some programmers will add the word "ref" to the name of a reference parameter as a reminder that it is a reference to the value of interest instead of a copy. 3. At the time of the call, the caller computes a pointer to the value of interest and passes that pointer. The type of the pointer (pointer to the value of interest) will agree with the type in (2) above. If the value of interest is local to the caller, then this will often involve a use of the & operator. 4. When the callee is running, if it wishes to access the value of interest, it must dereference its pointer to access the actual value of interest. Typically, this equates to use of the dereference operator (*) in the function to see the value of interest.
Computer Programming
Handout Lahore
The University of
Swap() Function
The values of interest for Swap() are two it . nTherefore, Swap() does not take it its as n s parameters. It takes a pointers to it (itIn the body of Swap() the parameters, a b, 's.* n n and are dereferenced with * to get at the actual (int ) values of interest.
Swap() Caller
To call Swap(), the caller must pass pointers to the values of interest...
The parameters to Swap() are pointers to values of interest which are back in the caller's locals. The Swap() code can dereference the pointers to get back to the caller's memory to exchange the values. In this case, Swap() follows the pointers to exchange the values in the variables x and y back in SwapCaller(). Swap() will exchange any two ints given pointers to those two ints.
Handout Lahore
The University of
storage from the callee back to its caller. When the callee exits, its local memory is deallocated and so the pointer no longer has a pointee. In the above, correct cases, we use & to pass a pointer from the caller to the callee. The pointer remains valid for the callee to use because the caller locals continue to exist while the callee is running. The pointees will remain valid due to the simple constraint that the caller can only exit sometime after its callee exits. Using & to pass a pointer to local storage from the caller to the callee is fine. The reverse case, from the callee to the caller, is the & bug.
Heap Memory
"Heap" memory, also known as "dynamic" memory, is an alternative to local stack memory. Local memory is quite automatic it is allocated automatically on function call and it is deallocated automatically when a function exits. Heap memory is different in every way. The programmer explicitly requests the allocation of a memory "block" of a particular size, and the block continues to be allocated until the programmer explicitly requests that it be deallocated. Nothing happens automatically. So the programmer has much greater control of memory, but with greater responsibility since the memory must now be actively managed. The advantages of heap memory are... Lifetime. Because the programmer now controls exactly when memory is allocated and deallocated, it is possible to build a data structure in memory, and return that data structure to the caller. This was never possible with local memory which was automatically deallocated when the function exited. Size. The size of allocated memory can be controlled with more detail. For example, a string buffer can be allocated at run-time, which is exactly the right size to hold a particular string. With local memory, the code is more likely to declare a buffer size 1000 and hope for the best. (See the StringCopy() example below.)
Computer Programming
The University of
More Work. Heap allocation needs to arranged explicitly in the code which is just more work. More Bugs. Because it's now done explicitly in the code, realistically on occasion the allocation will be done incorrectly leading to memory bugs. Local memory is constrained, but at least it's never wrong. Nonetheless, there are many problems that can only be solved with heap memory, so that's that way it has to be.
Each allocation request reserves a contiguous area of the requested size in the heap and returns a pointer to that new block to the program. Since each block is always referred to by a pointer, the block always plays the role of a "pointee" and the program always manipulates its heap blocks through pointers. The heap block pointers are sometimes known as "base address" pointers since by convention they point to the base (lowest address byte) of the block.
Computer Programming
Handout Lahore
The University of
In this example, the three blocks have been allocated contiguously starting at the bottom of the heap, and each block is 1024 bytes in size as requested. In reality, the heap manager can allocate the blocks wherever it wants in the heap so long as the blocks do not overlap and they are at least the requested size. At any particular moment, some areas in the heap have been allocated to the program, and so are "in use". Other areas have yet to be committed and so are "free" and are available to satisfy allocation requests. The heap manager has its own, private data structures to record what areas of the heap are committed to what purpose at any moment The heap manager satisfies each allocation request from the pool of free memory and updates its private data structures to record which areas of the heap are in use. Deallocation When the program is finished using a block of memory, it makes an explicit deallocation request to indicate to the heap manager that the program is now finished with that block. The heap manager updates its private data structures to show that the area of memory occupied by the block is free again and so may be re-used to satisfy future allocation requests. Here's what the heap would look like if the program deallocates the second of the three blocks...
After the deallocation, the pointer continues to point to the now deallocated block. The program must not access the deallocated pointee. This is why the pointer is drawn in gray the pointer is there, but it must not be used. Sometimes the code will set the pointer to NULL immediately after the deallocation to make explicit the fact that it is no longer valid.
Computer Programming
Handout Lahore
The University of
There is some "heap manager" library code which manages the heap for the program. The programmer makes requests to the heap manager, which in turn manages the internals of the heap. In C++, the heap is managed by the operators new and delete. The heap manager uses its own private data structures to keep track of which blocks in the heap are "free" (available for use) and which blocks are currently in use by the program and how large those blocks are. Initially, all of the heap is free. The heap may be of a fixed size (the usual conceptualization), or it may appear to be of a fixed but extremely large size backed by virtual memory. In either case, it is possible for the heap to get "full" if all of its memory has been allocated and so it cannot satisfy an allocation request. The allocation function will communicate this run-time condition in some way to the program usually by returning a NULL pointer or raising a language specific run-time exception. The allocation function requests a block in the heap of a particular size. The heap manager selects an area of memory to use to satisfy the request, marks that area as "in use" in its private data structures, and returns a pointer to the heap block. The caller is now free to use that memory by dereferencing the pointer. The block is guaranteed to be reserved for the sole use of the caller the heap will not hand out that same area of memory to some other caller. The block does not move around inside the heap its location and size are fixed once it is allocated. Generally, when a block is allocated, its contents are random. The new owner is responsible for setting the memory to something meaningful. The deallocation function is the opposite of the allocation function. The program makes a single deallocation call to return a block of memory to the heap free area for later re-use. Each block should only be deallocated once. The deallocation function takes as its argument a pointer to a heap block previously furnished by the allocation function. The pointer must be exactly the same pointer returned earlier by the allocation function, not just any pointer into the block. After the deallocation, the program must treat the pointer as bad and not access the deallocated pointee. Simple Heap Example Here is a simple example which allocates an it in the heap, stores the number in the block n block, and then deallocates it. This is the simplest possible example of heap block allocation, use, and deallocation. The example shows the state of memory at three different times during the execution of the above code. The stack and heap are shown separately in the drawing a drawing for code which uses stack and heap memory needs to distinguish between the two areas to be accurate since the rules which govern the two areas are so different. In this case, the lifetime of the local variable intPtr is totally separate from the lifetime of the heap block, and the drawing needs to reflect that difference.
Computer Programming
Handout Lahore
The University of
delete intptr;
Handout Lahore
The University of
dereferencing such an uninitialized pointer is a common, but catastrophic error. Sometimes this error will crash immediately (lucky). Other times it will just slightly corrupt a random data structure (unlucky). The call to delete operator deallocates the pointee as shown at T3. Dereferencing the pointer after the pointee has been deallocated is an error. Unfortunately, this error will almost never be flagged as an immediate run-time error. 99% of the time the dereference will produce reasonable results 1% of the time the dereference will produce slightly wrong results. Ironically, such a rarely appearing bug is the most difficult type to track down. When the function exits, its local variable intPtr will be automatically deallocated following the usual rules for local variables. So this function has tidy memory behavior all of the memory it allocates while running (its local variable, its one heap block) is deallocated by the time it exits.
Heap Array
In the C++ language, it's convenient to allocate an array in the heap, since C can treat any pointer as an array. The size of the array memory block is the size of each element (as computed by the sizeof() operator) multiplied by the number of elements. So the following code heap allocates an array of 100 sr the heap, sets them all to 22/7, 's u tin f and deallocates the heap array... Heap String Example Here is a more useful heap array example. The StringCopy() function takes a C string, makes a copy of that string in the heap, and returns a pointer to the new string. The caller takes over ownership of the new string and is responsible for freeing it.
Memory Leaks
What happens if some memory is heap allocated, but never deallocated? A program which forgets to deallocate a block is said to have a "memory leak" which may or may not be a serious problem. The result will be that the heap gradually fill up as there continue to be allocation requests, but no deallocation requests to return blocks for re-use.
Computer Programming
Handout Lahore
The University of
For a program which runs, computes something, and exits immediately, memory leaks are not usually a concern. Such a "one shot" program could omit all of its deallocation requests and still mostly work. Memory leaks are more of a problem for a program, which runs for an indeterminate amount of time. In that case, the memory leaks can gradually fill the heap until allocation requests cannot be satisfied, and the program stops working or crashes. Many commercial programs have memory leaks, so that when run for long enough, or with large data-sets, they fill their heaps and crash. Often the error detection and avoidance code for the heap-full error condition is not well tested, precisely because the case is rarely encountered with short runs of the program that's why filling the heap often results in a real crash instead of a polite error message. Most compilers have a "heap debugging" utility which adds debugging code to a program to track every allocation and deallocation. When an allocation has no matching deallocation, that's a leak, and the heap debugger can help you find them.
Ownership
StringCopy() allocates the heap block, but it does not deallocate it. This is so the caller can use the new string. However, this introduces the problem that somebody does need to remember to deallocate the block, and it is not going to be StringCopy(). That is why the comment for StringCopy() mentions specifically that the caller is taking on ownership of the block. Every block of memory has exactly one "owner" who takes responsibility for deallocating it. Other entities can have pointers, but they are just sharing. There's only one owner, and the comment for StringCopy() makes it clear that ownership is being passed from StringCopy() to the caller. Good documentation always remembers to discuss the ownership rules which a function expects to apply to its parameters or return value. Or put the other way, a frequent error in documentation is that it forgets to mention, one way or the other, what the ownership rules are for a parameter or return value. That's one way that memory errors and leaks are created.
Ownership Models
The two common patterns for ownership are... Caller ownership. The caller owns its own memory. It may pass a pointer to the callee for sharing purposes, but the caller retains ownership. The callee can access things while it runs, and allocate and deallocate its own memory, but it should not disrupt the caller's memory. Callee allocated and returned. The callee allocates some memory and returns it to the caller. This happens because the result of the callee computation needs new memory to be stored or represented. The new memory is passed to the caller so they can see the result, and the caller must take over ownership of the memory. This is the pattern demonstrated in StringCopy().
Computer Programming
Handout Lahore
The University of
Heap memory can be passed back to the caller since it is not deallocated on exit, and it can be used to build linked structures such as linked lists and binary trees. The disadvantage of heap memory is that the program must make explicit allocation and deallocate calls to manage the heap memory. The heap memory does not operate automatically and conveniently the way local memory does.
Computer Programming