Princeton University
COS 217: Introduction to Programming Systems

Assignment 6: A Heap Manager Module


Purpose

The purpose of this assignment is to help you understand how dynamic memory management works in C. It also will give you more opportunity to use the GNU/Unix programming tools, especially bash, emacs, gcc217, gdb, and make.


Rules

You may work with one partner on this assignment. You need not work with a partner, but we strongly prefer that you do. Your preceptor will help you with your partner search, if you so desire.

Your partner must be from your precept. Sorry, no exceptions. So make sure you find a partner as soon as you can. After all, if your precept has an odd number of students, then someone must work alone; don't let that person be you!

If you work with a partner, then only one of the partners should submit files. Your readme file, your makefile, and your source code files should contain both your name and your partner's name.

"Checking invariants" (as described below) is the "extra challenge" part of this assignment. While doing the "extra challenge" part of the assignment, you are bound to observe the course policies regarding assignment conduct as given in the course Policies web page, plus one additional policy: you may not use any "human" sources of information. That is, you may not consult with the course's staff members, the lab teaching assistants, other current students via Piazza, or any other people while working on the "extra challenge" part of an assignment, except for clarification of requirements. However you may consult with your partner, with no limitations.

The "extra challenge" part is worth 15 percent of this assignment. So if you don't do any of the "extra challenge" part and all other parts of your assignment solution are perfect and submitted on time, then your grade for the assignment will be 85 percent.


Background

A standard C programming environment contains four functions that allow management of the runtime heap: malloc, free, calloc, and realloc. Those heap management functions are used heavily in many C programs.

Section 8.7 of the book The C Programming Language (Kernighan and Ritchie) shows an implementation of the malloc and free functions. That book section is on electronic reserve at Princeton's library; you can access it through Princeton's Blackboard system (http://blackboard.princeton.edu) by selecting the COS 217 course and clicking on E-Reserves. The key data structure in that implementation is a circular singly-linked list; each free memory "chunk" is stored in that list. Each memory chunk contains a header which specifies its size and, if free, the address of the next chunk in the list. Although elegant in its simplicity, that implementation can be inefficient.

The web page http://gee.cs.oswego.edu/dl/html/malloc.html (Doug Lea) describes how one can enhance such an implementation so it is more efficient. (Here is a copy of that web page, just in case the original is unavailable.) The key data structure is an array of non-circular doubly-linked lists, that is, an array of "bins." Each bin contains all free chunks of a prescribed size. The use of multiple bins instead of a single linked list allows malloc to be more efficient.

Moreover, each memory chunk contains both a header and a footer. The header contains three fields: the size of the chunk, an indication of whether the chunk is free, and, if free, a pointer to the next free chunk in its bin. The footer contains two fields: the size of the chunk, and, if free, a pointer to the previous free chunk in its bin. That chunk structure allows free to be more efficient.

A more thorough description of the pertinent data structures and algorithms will be provided in lectures and precepts.


Your Task

You are given the interface of a HeapMgr (heap manager) module in a file named heapmgr.h. It declares two functions:

void *HeapMgr_malloc(size_t uiSize);
void  HeapMgr_free(void *pv);

You also are given three implementations of the HeapMgr module:

Your task is to create two additional implementations of the HeapMgr module. Your first implementation, heapmgr1.c, should enhance heapmgrbase.c so it is reasonably efficient. To do that it should use a single doubly-linked list and chunks that contains headers and footers (as described above, in lectures, and in precepts).

If designed properly, heapmgr1.c will be reasonably efficient in most cases. However, heapmgr1.c is subject to poor worst-case behavior. Your second implementation, heapmgr2.c, should enhance heapmgr1.c so the worst-case behavior is not poor. To do that it should use multiple doubly-linked lists, alias bins (as described above, in lectures, and in precepts).

Your HeapMgr implementations should not call the standard malloc, free, calloc, or realloc functions.

Your HeapMgr implementations should thoroughly validate function parameters by calling the standard assert macro.

Your HeapMgr implementations should check invariants by:


Logistics

Develop on nobel, using emacs to create source code and gdb to debug.

The directory /u/cos217/Assignment6 contains files that you will find useful:

The testheapmgr program requires three command-line arguments. The first should be any one of seven strings, as shown in the following table, indicating which of seven tests the program should run:

Argument Test Performed
LifoFixed LIFO with fixed size chunks
FifoFixed FIFO with fixed size chunks
LifoRandom LIFO with random size chunks
FifoRandom FIFO with random size chunks
RandomFixed Random order with fixed size chunks
RandomRandom Random order with random size chunks
Worst Worst case order for a heap manager implemented using a single linked list

The second command-line argument is the number of calls of HeapMgr_malloc and HeapMgr_free that the program should execute. The third command-line argument is the (maximum) size, in bytes, of each memory chunk that the program should allocate and free.

Immediately before termination testheapmgr prints to stdout an indication of how much CPU time and heap memory it consumed. See the testheapmgr.c file for more details.

To test your HeapMgr implementations, you should build two programs using these gcc217 commands:

gcc217 testheapmgr.c heapmgr1.c chunk.c -o testheapmgr1
gcc217 testheapmgr.c heapmgr2.c chunk.c -o testheapmgr2

To collect timing statistics, you should build five programs using these gcc217 commands:

gcc217 -O3 -D NDEBUG testheapmgr.c heapmgrgnu.c -o testheapmgrgnu
gcc217 -O3 -D NDEBUG testheapmgr.c heapmgrkr.c -o testheapmgrkr
gcc217 -O3 -D NDEBUG testheapmgr.c heapmgrbase.c chunkbase.c -o testheapmgrbase
gcc217 -O3 -D NDEBUG testheapmgr.c heapmgr1.c chunk.c -o testheapmgr1
gcc217 -O3 -D NDEBUG testheapmgr.c heapmgr2.c chunk.c -o testheapmgr2

The -O3 (that's uppercase "oh", followed by the number "3") argument commands gcc to optimize the machine language code that it produces. When given the -O3 argument, gcc spends more time compiling your code so, subsequently, the computer spends less time executing your code. The -D NDEBUG argument commands gcc to define the NDEBUG macro, just as if the preprocessor directive #define NDEBUG appeared in the specified .c file(s). Defining the NDEBUG macro disables the calls of the assert macro within the HeapMgr implementations. Doing so also disables code within testheapmgr.c that performs (very time consuming) checks of memory contents.

Create additional test programs as you deem necessary. You need not submit your additional test programs.

In the /u/cos217/Assignment6 directory you also will find files sampleheapmgr1.o and sampleheapmgr2.o. Those are object files that were built from correct heapmgr1 and heapmgr2 implementations using the -O3 and -D NDEBUG options. You can build programs using those heapmgr implementations (and the testheapmgr.c client), build programs using your heapmgr implementations (and the testheapmgr.c client), run the programs, and compare the output. Thereby you'll get a good sense of whether your implementations are correct.

More specifically, you'll discover that the the "CPU time" written by the programs will be inconsistent; that is, the CPU time consumed by any given program (with any given command-line arguments) will vary, sometimes significantly, based upon the current system load. However you'll discover that the "memory consumption" written by the programs will be consistent; that is, the memory consumed by any given program (with any given command-line arguments) will be the same each time you run the program. If your implementations consume exactly the same amount of memory as the given ones do, and sometimes consume approximately the same amount of time as the given ones do, then you can be reasonably sure that your implementations are correct.

Create a makefile. The first dependency rule of the makefile should build five executable files: testheapmgrgnu, testheapmgrkr, testheapmgrbase, testheapmgr1, and testheapmgr2. That is, the first dependency rule of your makefile should be:

all: testheapmgrgnu testheapmgrkr testheapmgrbase testheapmgr1 testheapmgr2

The makefile that you submit should:

We recommend that you create your makefile early in your development process. Doing so will allow you to use and test your makefile during development.

Critique your code using the splint tool. Each time splint generates a warning on your code, you should either (1) edit your code to eliminate the warning, or (2) explain your disagreement with the warning in your readme file.

Similarly, critique your code using the critTer tool. Each time critTer generates a warning on your code, you should either (1) edit your code to eliminate the warning, or (2) explain your disagreement with the warning in your readme file.

Create a readme file by copying the file /u/cos217/Assignment6/readme to your project directory, and editing the copy by replacing each area marked "<Complete this section.>" as appropriate.

In particular, in your readme file record the CPU times and heap memory consumed by testheapmgr using heapmgrgnu.c, heapmgrkr.c, heapmgrbase.c, heapmgr1.c, and heapmgr2.c, with tests RandomRandom and Worst, with call count 100000, and with maximum chunk sizes 1000 and 10000. Note that if the CPU time consumed is more than 5 minutes, testheapmgr will abort execution. To report the time and memory consumption, it is sufficient to paste the output of the testheap script into your readme file.

Submit your work electronically on nobel using the command:

submit 6 heapmgr1.c heapmgr2.c readme makefile

Grading

We will grade your work on quality from the user's and programmer's points of view. From the user's point of view, your program has quality if it behaves as it should. The correct behavior of your program is defined by the previous sections of this assignment specification.

From the programmer's point of view, your code has quality if it is well styled and thereby easy to maintain. In part, good style is defined by the splint and critTer tools, and by the rules given in The Practice of Programming (Kernighan and Pike) as summarized by the Rules of Programming Style document. The more course-specific style rules listed in the previous assignment specifications also apply. Proper function-level modularity will be a prominent part of your grade. To encourage good coding practices, we will deduct points if gcc217 generates warning messages.


critTer Warnings on Given Code

When critTer critiques the testheapmgr.c file, it generates warnings about the use of "magic numbers," the length of some loops, and the length of the file. We judged that using magic numbers, defining deeply nested code, and defining long loops in testheapmgr.c was clearer than the alternative. And splitting testheapmgr.c into multiple files would have complicated the build process unnecessarily. So please ignore those warnings.

Similarly, when critTer critiques the heapmgrbase.c file, it generates a warning which suggests that the HeapMgr_free function should validate its pv parameter through an assert. In fact the HeapMgr_free function should not validate its pv parameter through an assert, so please ignore that warning.


This assignment was written by Robert M. Dondero, Jr.