Managing the Heap
Published in Computer Language, February 1990
Introduction
Experienced C programmers generally have a love/hate relationship with the heap. The memory allocation routines used to allocate and free up memory from the heap are usually fast and efficient. There is very little overhead to contend with when allocating or accessing blocks. And most of the memory allocation routines are now standardized in the ANSI C draft, so code that uses these routines can be considered to be very portable.
But the simplicity underlying the heap memory routines can also be responsible for a host of nasty programming problems. Some of the heap use problems that have caused me a lot of trouble over the years include:
- Using space beyond the end of an allocated memory block. Or similarly, using space before the start of a memory block.
- Neglecting to free blocks when done with them, eventually resulting in an allocation failure.
- Fragmenting the heap. Since allocated memory blocks can’t be moved, fragmentation can cut way down on the largest block size available from the heap.
- Allocation problems caused by library modules that I don’t have control over.
These problems just represent my personal highlights. The heap presents the programmer with virtually limitless opportunities for error. The actual damage done is only limited by our creativity.
This article describes a system I have used that helps detect these and other common memory use errors. By simply adding a single include file to each module being tested, a heap manager debugger system is put in place, without requiring any modification of existing source code.
Know Your Enemy
The Standard C routines used for managing heap memory are:
void *malloc(size_t size) | This routine allocates a memory block whose size is defined in the argument. It returns a pointer to the newly allocated block. |
void *calloc(size_t nitems, size_t size) | This routine allocates enough storage to store nitems blocks of size size. It also initializes all of the memory to 0. |
void *realloc( void *block, size_t size) | This routine adjust the size of the block parameter to be size. A pointer to the new block is returned. |
void free(void *block) | This routine frees up block and returns it to the list of memory available for allocation on the heap. |
These routines are defined in the ANSI C draft, and are essentially identical under Unix and Xenix implementations. The problem with these routines are that they return a pointer that points directly into memory. This means that when data is accessed using one of these memory pointers, there is no mechanism in place to check that the pointer is being used correctly. A pointer can easily be incremented or indexed past the end of the allocated block, and start mangling data in another block. There is simply no validation done on pointer values. This is one reason programmers like using pointers in C: the lack of qualification makes memory access using pointers extremely fast.
The heap fragmentation problem is one that can be particularly frustrating, at least to those of
use operating on machines that aren’t graced with virtual memory. I have frequently been in the
situation where I need a relatively large block of memory, say 50K bytes. I know that my heap has
the space I need, because I allocated it previously in my program, and things haven’t changed
much since then. Yet when I call malloc()
, I get an error return. Careful examination usually
shows that I have a small block sitting in the middle of the heap, which breaks down my largest
possible block by 50%. Solving this problem requires that I learn a little bit more about the
structure of my heap, and how the heap managers work. With this knowledge in hand I can reorder
my calls to the heap manager, so my small blocks don’t cause this kind of fragmentation.
Solutions
A commonly suggested solution to the problem of heap fragmentation is the roll your own approach. This normally consists of replacing the standard memory allocation routines with ones that you have written. The replacement routines usually have more error checking built into them, and will often perform somewhat better than the ones supplied with the run time library.
This approach is useful, but suffers from a few problems. First of all, the run time library supplied with your compiler usually does some initialization of the heap. This involves determining what the initial size of the heap is, and setting up some global variables that control the heap. If any routines in the run time library look at or modify these global variables, you could have serious interaction problems.
Secondly, memory procedures differ between different operating systems and machines. A routine you write to work under MS-DOS may not work on a Sun Unix system. Word and pointer sizes are different, and O/S calls to get or release memory are different.
Third, your problems may be intimately related to the standard malloc()
type memory
allocation routines. Replacing them with debugging routines of your own creation may make the
problem impossible to recreate.
And finally, if you are using third party libraries, and don’t have the source, you have an
additional problem. If these routines call malloc()
and free()
, you are forced to duplicate
these functions, including names, parameters and return values. This is even true of some of the
run time library routines. Calling stringdup()
for example, usually generates a call to
malloc()
. So if you create new memory allocation routines, you still need to emulate malloc()
.
My Solution
My first step in tackling the heap was to create replacement memory allocation routines that I
could transparently move in and out of my existing code. This was simply a matter of creating a
header file called HEAP.H
that I included in all of my source files. HEAP.H
consisted of a three macro definitions that effectively replace malloc()
, calloc()
, and
free()
. The macro definitions are shown in Figure 1, along with the function prototypes for my
replacement routines.
HEAP.H
As you can see from these definitions, these routines will replace any of the three heap function calls made in each module with calls to my substitute routines.
Two additional parameters are automatically added in to the call: the file name and the file
number, represented by the predefined macros __FILE__
and __LINE__
. These will be a
tremendous help in keeping track of who allocates what pointers. Note that these two macros are
included in the ANSI draft standard, and seem to be in near universal use by now.
After recompiling all of my source modules with the new HEAP.H
file, I now should
have control over most of the memory allocation taking place in my program. The question then is,
what do I do in these routines?
Looking back at my frequent problems, I can come up with ways to address each of them. My first
problem was that of pointers writing past the end of their allocated area, or backing up and
writing before the start of their allocated memory. My approach to this problem is that in my two
memory allocation routines, my_calloc()
and my_malloc()
, I allocate an extra 16 bytes before
and after the memory block. For example, if the user has a call that request 1000 bytes, as in:
my memory allocation routine will instead call malloc()
to allocate 1032 bytes. The block that
malloc()
returns to my_malloc()
is partitioned into three segments.
The first 16 bytes are defined as the leading picket, and the last 16 bytes are defined as the
trailing picket. The 1000 bytes in between belong to the caller. So when malloc()
returns a
pointer to my_malloc()
, my_malloc()
will return the value of that pointer + 16 to the calling
routine. my_malloc()
also stores a fixed byte pattern in the picket area, which can be tested
for corruption at any time.
The 16 byte margin around the buffer should guarantee proper alignment for any type object on most machines. In addition, it will protect against pointer offsets that are off by a factor of only 1 or so bytes, which account for a large percentage of pointer errors.
My second problem was failure to free blocks after I am done with them. The problem here is that it is tough to keep track of all the different blocks that are currently allocated. Many run time libraries provide no way to determine which heap blocks are currently in use. Even if you can figure out which blocks are in use, just having a memory address doesn’t tell you which routine allocated the block.
The solution to this problem is to keep track of each block as it is allocated. The macro definitions provided me with a way to track where in the program each memory allocation occurred. All I have to do is add the file name and line number to a table as the elements are allocated. I created a table used by the heap routines that has the following structure:
The heap tag table keeps track of all this information for every pointer that is allocated. The first two items give the real address of the heap entry, as well as the address that was returned to the calling program, the size of the heap entry, and the file name and line number in the file. The final two entries, the maverick and tagged fields will be discussed later.
What this means is that if I faithfully add each pointer to the table when it is allocated, and just as faithfully remove each pointer from the tag table when it is freed, I will then have a way to know exactly how many pointers are in use, and where in my program they were allocated. Note also that I could easily extend this table to include even more information about when and where the pointer is allocated, it is simply a matter of adding more fields.
My third heap problem was fragmentation. Once again, keeping track of the tag table is going to let me address that problem. By knowing just where my pointers are, and who allocated them, I can put in place mechanisms to either move or free up the memory, cleaning up the heap in the process.
The final problem I had was with heap problems caused by libraries I don’t have the source code for. For example, there are dozens of routines in my compiler’s run time libraries that allocate memory. Third party libraries that I link into my code have lots of hidden allocation. I need to know what is being allocated, and how it is affecting my heap.
Since most people aren’t willing or able to spring for the added cost source code for their
libraries, recompiling the offending modules with the HEAP.H
header file is usually
not a possibility. Yet I still need to be able keep track of these modules. I would like to know
when and where they are allocated, and have some idea of when and where they are freed.
In my heap routines, I refer to heap blocks that were created outside the scope of my source code
as maverick blocks. My first problem is that of being able to detect maverick blocks.
Fortunately, Microsoft C provides a convenient mechanism for this. In their run time library they
have included a library function called _heapwalk()
. This routine lets you walk through the
entire heap, one block at a time. You eventually see each allocated block, and in addition you
are given the size of the block.
In order to find maverick blocks, I just take a walk through the heap using the _heapwalk()
function. For each pointer produced, I scan through my tag table, looking at the real_address
field, to see if I already have that block in my table. If it doesn’t show up in my tag table,
it means I have found a maverick block. I add it to the table, but set the maverick flag.
This flag tells me that I am not going to be able to check the pickets for this block, since it
doesn’t have any. It also tells me that I don’t know what the file name or line number for the
pointer is. But at least I know about its existence.
Once I have catalogued the maverick pointer in my tag table, I can keep track of it, and have
some idea of just when it is freed up. When I am taking my walk through the heap that I mentioned
above, I am setting the tag field for every entry that I match up with from the _heapwalk()
function. After I have finished my walk, I can scan the table and see if any pointers don’t have
their tag field set. Those are pointers that have been freed up behind my back, and I can set up
my program to notify me when this happens.
The Run Time Implementation
The final version of my_malloc()
is shown below. The first part of the routine makes a call to
the verify_heap()
routine. This is the module that walks through the heap, checking for new
maverick blocks as well as any blocks that may have disappeared. This routine also verifies that
the leading and trailing pickets for each of the non-maverick blocks are still intact. If this
routine finds any problems with the heap, it brings it to my attention with a call to
screen_message()
.
my_malloc()
screen_message()
is called by any of the routines in the heap module when it has something to
report. When screen_message()
is first invoked, it saves off the screen that the application
currently has displayed and pops up a display of the tag table. The screen routines in
SCREEN.C
are fairly rudimentary, and I won’t go into any detail on how they work.
The hardware dependent portions of the routine are isolated so that they ought to be easy to
modify to work under any number of platforms. Systems using an ASCII terminal probably would want
to have the heap debug output sent to a second terminal.
A sample screen from the test program that follows is shown in Figure 4. I use the ‘u’ and ‘d’ keys to page up and down through the tag table. The screen that is shown here has a single message, notifying me that it has located a maverick heap element, and added it to the tag table.
----------------------------------------------------------------------------
|Tag #| File Name |Line #| Picket | Address | Size | Picket |
|-----+------------+------+---------------+---------+------+---------------|
|0 |main.c |16 |0123456789ABCDE|4C86:001C|1000 |FEDCBA987654321|
|1 |main.c |17 |0123456789ABCDE|4C86:0426|2000 |FEDCBA987654321|
|2 |MAVERICK |0 | ***MAVERICK***|4C86:0C08|16 | ***MAVERICK***|
|3 |Not in use |0 | | NULL |0 | |
|4 |Not in use |0 | | NULL |0 | |
|5 |Not in use |0 | | NULL |0 | |
|6 |Not in use |0 | | NULL |0 | |
|7 |Not in use |0 | | NULL |0 | |
|8 |Not in use |0 | | NULL |0 | |
|9 |Not in use |0 | | NULL |0 | |
|10 |Not in use |0 | | NULL |0 | |
|11 |Not in use |0 | | NULL |0 | |
|12 |Not in use |0 | | NULL |0 | |
|13 |Not in use |0 | | NULL |0 | |
|14 |Not in use |0 | | NULL |0 | |
----------------------------------------------------------------------------
Found a maverick heap entry: 4C86:0C08
In this case, the maverick heap entry was created by a strdup()
function call. The test program
that created the heap error is shown in Figure 5. Now that the pointer has been entered in the
tag table, I will at least be able to keep an eye on it, and be aware of its existence.
The verify_heap()
routine not only walks through the heap looking for maverick and missing
pointers, it also checks to be sure that the pickets for all of the blocks are intact. If they
have been corrupted, an error message like that shown in Figure 6 pops up, showing what pointer
sustained the damage to its block.
I call the verify_heap()
routine at the entrance of all of my heap routines. That way I will be
able to check for any problems in the heap before I modify it. Note that in the MS-DOS world,
verify_heap()
could also check the null pointer memory area for modification.
----------------------------------------------------------------------------
|Tag #| File Name |Line #| Picket | Address | Size | Picket |
|-----+------------+------+---------------+---------+------+---------------|
|0 |main.c |12 |0123456789ABCDE|3BF6:276C|1000 |FEDCBA987654321|
|1 |main.c |13 |0123456789ABCDE|3BF6:2B76|2000 |XEDCBA987654321|
|2 |MAVERICK |0 | ***MAVERICK***|3BF6:3358|16 | ***MAVERICK***|
|3 |main.c |15 |0123456789ABCDE|3BF6:337A|3000 |FEDCBA987654321|
|4 |Not in use |0 | | NULL |0 | |
|5 |Not in use |0 | | NULL |0 | |
|6 |Not in use |0 | | NULL |0 | |
|7 |Not in use |0 | | NULL |0 | |
|8 |Not in use |0 | | NULL |0 | |
|9 |Not in use |0 | | NULL |0 | |
|10 |Not in use |0 | | NULL |0 | |
|11 |Not in use |0 | | NULL |0 | |
|12 |Not in use |0 | | NULL |0 | |
|13 |Not in use |0 | | NULL |0 | |
|14 |Not in use |0 | | NULL |0 | |
----------------------------------------------------------------------------
Error in trailing picket, tag 1
Your Implementation
Their are only two portions of this program that need to be customized for different platforms.
First, the screen routines in SCREEN.C
need to be adapted to suit your particular
hardware. The second part of the code that needs customization is the heap walk code in
HEAP.C
. Microsoft supplies a routine to walk through the heap, but most other PC
based compilers don’t have this routine. If you have the source code to the run time library it
ought to be simple to build this routine. After all, every call to malloc()
has to do some sort
of heap walk while it looks for free blocks. If you don’t have the source, and can’t build this
routine up, you can just eliminate it from the debugging routines. That means you won’t be able
to keep track of maverick blocks, but everything else will work just fine.
Conclusions
It would be nice if compiler companies would include some heap debugging tools with their run time libraries, especially in light of the frequency of heap problems. As long as they won’t, we have to resort to add on routines like the ones shown here. These routines have helped me find some stubborn heap problems, and with any luck they will prove useful for you.