Pointers And Memory - Contents
A D V E R T I S E M E N T
Here is a simple example which allocates an int block in the heap, stores the number 42
in the 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.
void Heap1() {
int* intPtr;
// Allocates local pointer local variable (but not its pointee)
// T1
// Allocates heap block and stores its pointer in local variable.
// Dereferences the pointer to set the pointee to 42.
intPtr = malloc(sizeof(int));
*intPtr = 42;
// T2
// Deallocates heap block making the pointer bad.
// The programmer must remember not to use the pointer
// after the pointee has been deallocated (this is
// why the pointer is shown in gray).
free(intPtr);
// T3
Simple Heap Observations
- After the allocation call allocates the block in the heap. The program
stores the pointer to the block in the local variable intPtr. The block is the
"pointee" and intPtr is its pointer as shown at T2. In this state, the pointer
may be dereferenced safely to manipulate the pointee. The pointer/pointee
rules from Section 1 still apply, the only difference is how the pointee is
initially allocated.
- At T1 before the call to malloc(), intPtr is uninitialized does not have a
pointee — at this point intPtr "bad" in the same sense as discussed in
Section 1. As before, 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 free() 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 (Section 2). 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 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.
/*
Given a C string, return a heap allocated copy of the string.
Allocate a block in the heap of the appropriate size,
copies the string into the block, and returns a pointer to the block.
The caller takes over ownership of the block and is responsible
for freeing it.
*/
char* StringCopy(const char* string) {
char* newString;
int len;
len = strlen(string) + 1; // +1 to account for the '\0'
newString = malloc(sizeof(char)*len); // elem-size * number-of-elements
assert(newString != NULL); // simplistic error check (a good habit)
strcpy(newString, string); // copy the passed in string to the block
return(newString); // return a ptr to the block
}
Heap String Observations
StringCopy() takes advantage of both of the key features of heap memory...
- Size. StringCopy() specifies, at run-time, the exact size of the block
needed to store the string in its call to malloc(). Local memory cannot do
that since its size is specified at compile-time. The call to
- sizeof(char) is not really necessary, since the size of char is 1 by
definition. In any case, the example demonstrates the correct formula for
the size of an array block which is element-size * number-of-elements.
- Lifetime. StringCopy() allocates the block, but then passes ownership of it
to the caller. There is no call to free(), so the block continues to exist even
after the function exits. Local memory cannot do that. The caller will need
to take care of the deallocation when it is finished with the string.
Memory LeaksWhat 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.
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 ModelsThe 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().
Heap Memory Summary
Heap memory provides greater control for the programmer — the blocks of memory can
be requested in any size, and they remain allocated until they are deallocated explicitly.
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.
Back to Table of Contents
A D V E R T I S E M E N T
|
Subscribe to SourceCodesWorld - Techies Talk |
|