Lab 5: Writing a Dynamic Memory Allocator

Assigned Monday, May 21
Due Date Friday June 1
Files lab5.tar
Videos You may find the following videos helpful for getting started with the lab:
Submissions Submit your files via the Canvas assignments page (go to the Labs section, not the Homeworks section). Be sure to follow the detailed instructions at the bottom of this page.

Learning Objectives and Overview

In this lab, you will be writing a dynamic storage allocator for C programs, i.e., your own version of the malloc and free routines. This is a classic implementation problem with many interesting algorithms and opportunities to put several of the skills you have learned in this course to good use. It is quite involved. Start early!

Code for this lab

Running tar xvf lab5.tar from the terminal will extract the lab files to a directory called lab5.

Instructions

The only file you will modify and turn in is mm.c (unless you decide to do extra credit). You may find the short README file useful to read.

(In the following instructions, we will assume that you are executing programs on the CSE VM or in your local directory on attu. For this lab, you can work anywhere there's a C compiler and make.)

Your dynamic storage allocator will consist of the following three functions (and several helper functions), which are declared in mm.h and defined in mm.c:

int   mm_init(void);
void* mm_malloc(size_t size);
void  mm_free(void* ptr);

The mm.c file we have given you partially implements an allocator using an explicit free list. Your job is to complete this implementation by filling out mm_malloc and mm_free. The three main memory management functions should work as follows:

We will compare your implementation to the version of malloc supplied in the standard C library (libc). Since the libc malloc always returns payload pointers that are aligned to 8 bytes, your malloc implementation should do likewise and always return 8-byte aligned pointers.

Provided Code for the Allocator

We define a BlockInfo struct designed to be used as a node in a doubly-linked explicit free list, and the following functions for manipulating free lists:

In addition, we implement mm_init and provide two helper functions implementing important parts of the allocator:

Finally, we use a number of C Preprocessor macros to extract common pieces of code (constants, annoying casts/pointer manipulation) that might be prone to error. Each is documented in the code. You are welcome to create your own macros as well, though the ones already included in mm.c are the only ones we used in our sample solution, so it is possible without more. For more info on macros, check the GCC manual and/or two of the videos posted at the top of this page.

Additionally, for debugging purposes, you may want to print the contents of the heap. This can be accomplished with the provided examine_heap() function.

Provided Code for the Underlying Memory System

The memlib.c package simulates the memory system for your dynamic memory allocator. In your allocator, you can call the following functions (if you use the provided code for an explicit free list, most uses of the memory system calls are already covered).

Provided Code for a Trace-Driven Driver Program

The driver program mdriver.c tests your mm.c package for correctness, space utilization, and throughput. Use the command make to generate the driver code and run it with the command ./mdriver -V (the -V flag displays helpful summary information as described below).

The driver program is controlled by a set of trace files that it will expect to find in a subdirectory called traces. The .tar. file provided to you should unpack into a directory structure that places the traces subdirectory in the correct location relative to the driver. (If you want to move the trace files around, you can update the TRACEDIR path in config.h). Each trace file contains a sequence of allocate and free directions that instruct the driver to call your mm_malloc and mm_free routines in some sequence. The driver and the trace files are the same ones we will use when we grade your submitted mm.c file.

The mdriver executable accepts the following command line arguments:

Programming Rules

Collaboration

This is all a reminder, but an important one: In general we encourage students to discuss ideas from the labs and homeworks. But the code you submit must be your own. You must not refer to solutions from previous quarters or from a similar course at another university -- that is cheating. You must not consult a fellow student's solution -- that is also cheating.

We encourage you to start early and ask questions as needed. The course staff is here to help you succeed. But even if you do end up struggling and not completing the assignment to your full satisfaction, submitting a partial solution is much better than cheating.

Evaluation

Your grade will be calculated (as a percentage) out of a total of 64 points as follows:

Hints

Getting Started

Debugging

Heap Consistency Checker

This is an optional, but recommended, addition that will help you check to see if your allocator is doing what it should (or figure out what it's doing wrong if not). Dynamic memory allocators are notoriously tricky beasts to program correctly and efficiently. They are difficult to program correctly because they involve a lot of untyped pointer manipulation. In addition to the usual debugging techniques, you may find it helpful to write a heap checker that scans the heap and checks it for consistency.

Some examples of what a heap checker might check are:

Your heap checker will consist of the function int mm_check(void) in mm.c. Feel free to rename it, break it into several functions, and call it wherever you want. It should check any invariants or consistency conditions you consider prudent. It returns a nonzero value if and only if your heap is consistent. This is not required, but may prove useful. When you submit mm.c, make sure to remove any calls to mm_check as they will slow down your throughput.

Extra Credit

As optional extra credit, implement a final memory allocation-related function: mm_realloc. Write your implementation in mm-realloc.c.

The signature for this function, which you will find in your mm.h file, is:

extern void* mm_realloc(void* ptr, size_t size);

Similarly, you should find the following in your mm-realloc.c file:

void* mm_realloc(void* ptr, size_t size) {
	// ... implementation here ...
}

To receive credit, you should follow the contract of the C library's realloc exactly (pretending that malloc and free are mm_malloc and mm_free, etc.). The man page entry for realloc says:

The realloc() function changes the size of the memory block pointed to by
ptr to size bytes.  The contents will be unchanged in the range from the
start of the region up to the minimum of the old and new sizes.  If the
new size is larger than the old size, the added memory will not be
initialized.  If ptr is NULL, then the call is equivalent to
malloc(size), for all values of size; if size is equal to zero, and ptr
is not NULL, then the call is equivalent to free(ptr).  Unless ptr is
NULL, it must have been returned by an earlier call to malloc(), calloc()
or realloc().  If the area pointed to was moved, a free(ptr) is done.

A good test would be to compare the behavior of your mm_realloc to that of realloc, checking each of the above cases. Your implementation of mm_realloc should also be performant. Avoid copying memory if possible, making use of nearby free blocks. You should not use memcpy to copy memory; instead, copy WORD_SIZE bytes at a time to the new destination while iterating over the existing data.

To run tracefiles that test mm_realloc, compile using make mdriver-realloc. Then, run mmdriver-realloc with the -f flag to specify a tracefile, or first edit config.h to include additional realloc tracefiles (realloc-bal.rep and realloc2-bal.rep) in the default list.

Don't forget to submit your finished mm-realloc.c along with mm.c.

Extra Extra Credit

Do NOT spend time on this part until you have finished and turned in the core assignment.

In this extra credit portion, you will implement a basic mark and sweep garbage collector. Write your implementation in mm-gc.c.

Some additional notes:

Don't forget to submit your finished mm-gc.c along with mm.c.

Lab 5 Reflection

Compile this code on attu or the CSE VM by saving it to, for example, lab5reflect.c and compiling it with gcc -Wall lab5reflect.c -o hmm.
#include <stdlib.h>
#include <stdio.h>

// Assume called with one argument that is a positive number
int main(int argc, char**argv) {
  int *p, *q;
  p = (int*)malloc(sizeof(int));
  *p = 42;
  free(p);
  q = (int*)malloc(atoi(argv[1])*sizeof(int));
  *q = 43;
  *p = 44;
  printf("%d\n",*q);
  free(q);
}
  1. This program has a bug. Explain what it is. [1pt]
  2. If you run this buggy program with argument 2 (i.e., ./hmm 2), what happens? Give a plausible description of what malloc and free might be doing that leads to this behavior. [1pt]
  3. If you run this buggy program with argument 20 (i.e., ./hmm 20), what happens? Give a plausible description of what malloc and free might be doing that leads to this behavior. [1pt]
  4. If you run this buggy program with argument 40 (i.e., ./hmm 40), what happens? Give a plausible description of what malloc and free might be doing that leads to this behavior. [1pt]

Submission Instructions

Submit the following files:

  1. mm.c
  2. mm-realloc.c [if you did the extra credit]
  3. mm-gc.c [if you did the extra extra credit]
  4. lab5reflect.txt

Once you're satisfied with your solutions, submit them via Canvas (link is at the top of this page).