CSci 230: Computing Systems Organization
Home Syllabus Readings Assignments Tests

Assignment 10: Writing malloc

Due: 5:00pm, Friday, November 7. Value: 45 pts.

Your assignment is to complete two subroutines malloc and free using the technique described under Memory allocation below. Such implementations would allow a program to use dynamic memory allocation.

I am providing you with three files with which to start.

heap.s The file that you should modify and submit for this assignment. It contains stubs for the subroutines you will write. You should not create or modify other files.
test.s The main file you would load into aas for testing your subroutines of heap.s. The testing process is described under Test code below.
io-sub.s Contains I/O subroutines. These are similar to those of the earlier assignment, but these use the ARM calling convention seen in class. Also included is the subroutine printHex. The test.s file uses these subroutines.

Using breakpoints is useful for this assignment. In particular, you may want to enter break doTest so that the program pauses each time it begins another iteration of the testing process. At that point, you may want to use list to view memory; list 800 840 displays the first 64 bytes in the heap.

Memory allocation

In this assignment we'll use a heap starting at memory address 0x800 and containing 512 (= 0x200) bytes.

Our heap will be a succession of blocks, some allocated and some free. Each block will have a length that is a multiple of four, and it will be preceded by a four-byte header specifying how long the block is, including the header. Note that since lengths are always a multiple of 4, the 1's and 2's bit of each block length will always be 0. Rather than always store a 0 in the 1's bit, though, we'll use the header's 1's bit to indicate whether the block is allocated (1) or available (0).

The malloc routine works as follows. Given a desired allocation length, it first computes the required block length by adding 4 bytes for the header and rounding up to the next multiple of 4. It then starts at address 0x800 and steps through the blocks until it finds an available block of at least the required length. If the block is precisely of the correct length, it marks the block allocated. More likely, the available block will be too long, and it will split the block into two pieces, of which the first is marked as the allocated block. In either case, the address it returns is the address of the first byte following the header.

The free routine is given the address of the first byte following a block's header. It clears the bit in the block's header so that the block is marked available. Also, if the block following the newly freed block is itself marked as unallocated, the subroutine should merge the two unallocated blocks into one. (We won't worry about whether to merge with the preceding block is free, since there's no easy way to identify where it is.)

Below is an example of how the heap would be set up by these subroutines after initializing the heap, allocating 10 bytes (which returns 0x804), allocating 25 bytes (which returns 0x814), freeing the first allocation, and finally freeing the second allocation.

addr   initial   malloc(10) malloc(25) free(0x804) free(0x814)
0x800 0x200 0x011 0x011 0x010 0x010
0x804 0 0 0 0 0
0x808 0 0 0 0 0
0x80c 0 0 0 0 0
0x810 0 0x1f0 0x021 0x021 0x1f0
0x814 0 0 0 0 0
0x818 0 0 0 0 0
0x81c 0 0 0 0 0
0x820 0 0 0 0 0
0x824 0 0 0 0 0
0x828 0 0 0 0 0
0x82c 0 0 0 0 0
0x830 0 0 0x1d0 0x1d0 0
0x834 0 0 0 0 0
: : : : : :

(In the last column, we merged the blocks, and in the process I removed the header of the second block (at 0x830). This is not strictly necessary, and it does add a needless memory access. But I recommend doing so to help with debugging.)

Though it's not displayed in the above memory diagrams, the test program actually writes into the first byte of each memory allocation. The value stored in the byte is the index of the entry that references the allocated block. The test program clears this byte before calling free.

Test code

The testing code, summarized by the C code below (though it is actually written in ARM assembly), uses an array of 16 entries. The program repeatedly selects a random entry from the array. If the entry is zero, the program calls malloc with a random parameter between 1 and 32 and places the returned address into the array. And if it is nonzero, it passes the value found into free and clears the array entry to zero.

void *allocs[16];
int i;
int size;
for (i = 0i < 16i++) allocs[i] = NULL;
while (1) {
    i = random() % 16// random number from 0 to 15
    if (allocs[i] == NULL) {
        size = 1 + random() % 32// random # bytes from 1 to 32
        allocs[i] = malloc(size);
        *((char*) allocs[i]) = (chari// mark first byte of allocation
        printf("%x+%3x/%x\n"iallocs[i], size);
    } else {
        printf("%x-%3x\n"iallocs[i]);
        *((char*) allocs[i]) = (char0// clear first byte of allocation
        free(allocs[i]);
        allocs[i] = NULL;
    }
}

The test code produces output as it goes, which you can use for verifying that your implementations of malloc and free work as they should. If the implementation is correct, you should see the below as the first fifty lines of output.

f+804/a
3+814/19
3-814
6+814/7
8+820/17
6-814
a+83c/1a
a-83c
7+83c/f
9+850/1e
    
7-83c
d+874/1b
1+814/5
9-850
a+83c/5
4+850/5
b+85c/9
5+894/11
b-85c
b+8ac/16
    
f-804
4-850
7+804/b
b-8ac
c+850/13
2+8ac/17
4+8c8/1f
b+848/3
d-874
9+874/14
    
9-874
5-894
5+874/9
d+894/f
0+8ec/11
0-8ec
6+8ec/d
9+900/15
f+91c/18
1-814
    
b-848
7-804
b+804/f
f-91c
0+91c/10
e+930/13
0-91c
2-8ac
f+868/7
1+884/7

Each line starts with the index of the entry (0 to f) processed during the iteration, followed by + for a call to malloc and - for a call to free. Next is the memory address returned by malloc or passed into free. Finally, for lines corresponding to malloc, there is a slash and the length of the allocated block in hexadecimal.