



Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Community
Ask the community for help and clear up your study doubts
Discover the best universities in your country according to Docsity users
Free resources
Download our free guides on studying techniques, anxiety management strategies, and thesis advice from Docsity tutors
Material Type: Assignment; Class: Data Struct & Algorithms; Subject: Computer Science; University: University of San Francisco (CA); Term: Spring 2005;
Typology: Assignments
1 / 7
This page cannot be seen from the preview
Don't miss anything!
Department of Computer Science University of San Francisco
Recall that the heap is the memory used by the system to service requests for memory made by the new operation in Java. Also recall that memory that has been allocated from the heap that is no longer being used by the program is periodically returned to the heap by the Java garbage collector. In languages such as C and C++ memory allocated from the heap that is no longer in use should be explicitly returned to the heap by the program. In programming assignment 2 we’ll look at how one might write software for explicitly allocating and deallocating from the heap. One approach involves the use of a linked list that’s stored in the heap: each available block of contiguous memory stores two pieces of information: its size and the address of the next available block of memory. If the address of the first block is also stored, then the sequence of available blocks forms a linked list. For example, suppose the heap consists of 16 memory locations, and addresses 2, 3, 6, 7, and 10–15 are free. Then, together with the address 2 — the address of the first free block — our “linked list” might look something like this:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents X X 6 2 X X 10 2 X X -1 6
In this diagram, memory that has been allocated (addresses 0, 1, 4, 5, 8, and 9) is marked with an ‘X’, and the first two addresses in each free block store the address of the next free location and the size of the free block, respectively. So address 2 stores 6, since address 6 is the beginning of the next free block, and address 3 stores 2 since the first free block has 2 elements. Note that the first location in the last free block (location 10) stores a -1. In our examples we’re using this value to indicate that there are no more blocks of free storage. When a program makes a request for memory, the system can traverse the linked list of free blocks until it finds one that’s big enough to satisfy the new request. In our example, if a request is made for 4 locations, the memory management software would start with the first free block (address 2), see that it only has 2 locations available, continue to the block starting at address 6, see that it also only has 2 locations available, and, finally, see that the block starting at address 10 with 6 locations is big enough to satisfy the request. In order to satisfy the request, the system simply reduces the size of the block starting at location 10 by four and returns the address 12:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents X X 6 2 X X 10 2 X X -1 2 X X X X
Notice that in our example we’re allocating memory from the end of the block. Things get more complicated if a request is made for an entire block. For example, if a program requests 2 locations, the entire block starting in location 2 can be used, and the address of the first free block will have to be changed to 6:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents X X X X X X 10 2 X X -1 2 X X X X In general, if an entire block is requested, the address stored in the preceding free block will have to be changed. For example, suppose the heap looks like this (e.g., addresses 4–5 and 0– have been freed):
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 4 2 X X 10 4 X X -1 2 X X X X
If a request is now made for a block of size 4, the entire block starting at address 4 will be allocated, and the address stored in 0 will have to be changed to 10:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 10 2 X X X X X X X X -1 2 X X X X When memory is freed, a new block is “inserted” into the linked list. For example, if a block of size 2 starting at address 6 is freed, then the updated memory should look like this:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 6 2 X X X X 10 2 X X -1 2 X X X X
Notice that the address stored in the free block at 0, the immediately preceding block, is updated to reference the new block, and the address in the newly freed block is the old address stored in the immediately preceding block at address 0. In general, when a block is freed, simply “inserting” it in the linked list won’t be enough. For example, if a block of size 2 starting at address 4 is now freed, and simply inserted into the list, we’ll have the following layout:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 4 2 X X 6 2 10 2 X X -1 2 X X X X
The problem here is that all the blocks have size 2, and a request for a block of size 4 will be (incorrectly) refused. So after a freed block is inserted into the list, the system should see if it can be merged with the following or preceding blocks (or both). In our example, a correct freeing of the block at address 4 will continue by merging the block at 4 and the block at 6:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 4 2 X X 10 4 X X -1 2 X X X X
If we free a block of size 2 at address 12, we should first create a new block:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 4 2 X X 10 4 X X 10 2 -1 2 X X
and then merge the block at 10 and the block at 12:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 4 2 X X 10 4 X X -1 4 X X
Finally, if we free the block of size 2 at address 2, we should first insert the new block:
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 2 2 4 2 10 4 X X -1 4 X X
It is possible that the user will request a block of memory larger than any available block, in which case your allocate method should return the constant NO ADDRESS. However, you may assume that the user will not try to free memory that hasn’t been allocated. You also don’t need to check whether a freed block contains memory that belongs to another free block. If the user enters an invalid command, the program should just prompt for a correct command and continue.
The header of a block in the free list is the two ints consisting of the address of the next block and the size of the block. It is important that there be enough room for this header in any block of memory in the free list. So any block of memory in the free list must be capable of storing at least two ints. It might at first seem that this could be dealt with by simply insisting that the user always allocate blocks of size at least two. However, this isn’t enough. For example, suppose the user requests a block of size 7 when memory has the following configuration.
Address 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Contents 10 8 X X -1 4 X X
If any request of size at least two is allowed, then the block starting at address 1 will be allocated, leaving a free block of size 1 at address 0, and we can’t store both the address of the next block and the size of the block. The solution is simple, though. Just make sure that any operation on memory — an allocate or release — operates on a block that is a multiple of the header size. In our examples, the header size is 2. So you could simply add the following code to the beginning of the allocate and release methods:
if (size % 2 != 0) size++;
A somewhat more robust approach allows for the possibility that the block header might change (e.g., if we wanted a doubly linked free list):
int addon = (HEADER_SIZE - (size % HEADER_SIZE)) % HEADER_SIZE; size = size + addon;
Here HEADER SIZE is a final member of the Memory class. In this connection note that it’s essential that the size of the memory allocated by the constructor also be a multiple of the header size. There are many possible approaches to the design of the allocate and release methods. Here’s the design I used:
// allocate Make sure the size is a multiple of the header size. Search for a block containing at least the desired memory. If a block was found { Compute the address of the block to be returned. Update the size of the block being used to service the request. If the new block size is zero Adjust the links in the free list. }
return the address of the block or NO_ADDRESS
// release Make sure the size is a multiple of the header size. If all memory is being used { Create a new block with the freed block. Update the address of the first free block. } else if the address of the first free block is greater than the address of the freed block { Create a new block with the freed block. Update the address of the first free block. Update the free list by merging blocks as necessary. } else { // freed block follows first block Find the immediate predecessor of the block being freed. Create a new block with the freed block. Update the next block address in the immediate predecessor of the freed block. Merge the new block and the following block as necessary. Merge the new block and its immediate predecessor as necessary. }
You do not have to use these designs. To the contrary, I encourage you to look for more efficient approaches. For example, as I described it, the allocate method searches for the first block that’s big enough to satisfy the request, and it might be better to look instead for a block that fits the request as closely as possible. For example, if there’s a request for a block of size 4, and there are free blocks of sizes 8 and 4, in this order, then the algorithm I used would return half of the block of size 8. This will leave two blocks of size 4, and a subsequent request for a block of size 8 will fail. If the algorithm returned the second block, the block of size 4, a subsequent request for a block of size 8 could be fulfilled.
You can get up to 10 points extra credit by using your class to implement a multiset of ints with a linked list. A multiset is like a set, except that it allows duplicates. For example, the set { 1 , 2 , 2 , 3 } is the same as the set { 1 , 2 , 3 }, but the multiset { 1 , 2 , 2 , 3 } is different from the multiset { 1 , 2 , 3 }. The operations you should implement are
In order to receive full credit, your program must be submitted electronically by 3:00 pm on Wednesday, March 2nd, and you must turn in printouts of your source files by 6 pm on the 2nd. You can receive half credit if you submit your program and turn in a print out after 3:00 pm Wednesday but before 3:00 pm Thursday. Programs submitted later than 3:00 pm Thursday, March 3, will receive no credit.
Correctness will be 60% of your grade. Does your program correctly allocate, release, and print the contents of the free list? Does it correctly handle requests for unavailable memory? The following static features will be graded.
It is OK for you to discuss solutions to this program with your classmates. However, no collaboration should ever involve looking at one of your classmate’s source programs! It is usually extremely easy to determine that someone has copied a program, even when the indivdual doing the copying has changed identifiers and comments. If we discover that someone has copied a program, the authors of both programs will receive an F in the course. Repeat offenders will be referred to the Academic Honesty Committee for additional disciplinary action.