NOTE: Whenever you need to add a specific number of bytes to a memory address pointing to a Chunk
struct, you should cast this memory address to either void*
or char*
. This ensures that, during pointer arithmetic, the operation adds bytes
rather than bytes * sizeof(Chunk)
. While you cannot dereference a void*
pointer, performing pointer arithmetic on it treats each increment as a one-byte move.
Compiling mymalloc.c
The gradescript for jmalloc compiles mymalloc.c along with a test file named test.c (which is copied from the Gradescript-Examples directory depending on the problem number provided to the script). This compilation step produces an executable named a.out by default, as no output file name is specified in the gcc command. If the compilation is successful, the script runs a.out and captures its standard output and standard error into temporary files for analysis. The test file (corresponding to a specific problem number from the Gradescript-Examples directory) is designed to test the functionality implemented in mymalloc.c for the given problem number. In other words, you don't need to use a makefile to compile your program. Just ./gradeall or ./gradescript number, but make sure that your mymalloc.c file is in the same directory as the gradescript file.
Variable and Struct
sbrk
, but that are available (free) to give to the user once they call my_malloc()
.
Each Chunk of memory in the free list data structure contains the size of the chunk, a pointer to the next Chunk, and a pointer to the previous Chunk.
NULL
, it indicates that there is either no free list of chunks or there is no chunk available that is large enough to satisfy the requested size.next
and prev
pointers of certain chunks to ensure the free list remains properly linked. The size of each chunk initially remains the same. However, subsequent operations, such as coalescing contiguous free chunks, may adjust the sizes of nodes in the list to optimize memory usage and minimize fragmentation.head == NULL
), create it. Like in this example provided by Dr. Plank: malloc_head ==
0x6100 (chunk that we passed to the function)
0x6100 (start of heap) | |
---|---|
8192 | 0x6100 |
NULL | 0x6104 |
NULL | 0x6108 |
... | ... |
0x8100 (end of heap -- sbrk(0)) |
Namely, point head
to the chunk of memory we passed to the function.
head
:
next
field of the chunk you want to insert to the current head
. previous
field of the current head
to the chunk you want to insert. new head
to be equal to the the chunk you want to insert. malloc_head ==
0x6100 (new head - chunk that we passed to the function)
0x6100 (start of heap) | |
---|---|
24 | 0x6100 |
0x6128 | 0x6104 |
NULL | 0x6108 |
0x6824abcd | 0x610c |
0x6110 | |
5555 | 0x6114 |
16 | 0x6118 |
NULL | 0x611c |
0x00230000 | 0x6120 |
0xfe?????? | 0x6124 |
8152 | 0x6128 (previous head) |
NULL | 0x612c |
0x6100 | 0x6130 |
... | ... |
0x8100 (end of heap -- sbrk(0)) |
I took this table from Dr. Plank's examples in the malloc 2 lecture. Only pay attention to the sections colored with red and blue and if you want to understand the whole memory layout read his Malloc Lecture #2.
NOTE: Sorting the list in ascending order makes coalescing easier.
size = (size + 7 + 8) & -8;
Alignment Example:
Suppose size
= 13. You want to allocate at least 13 bytes plus space for bookkeeping, and align everything to a multiple of 8.
-8
to round down to an exact multiple of 8. This step clears the last three bits of the size, ensuring it is a multiple of 8.
size = 13
size + 7 = 20 (0x14)
size + 7 + 8 = 28 (0x1C)
0x1C
(binary: 00011100)
-8 decimal => 0xFFFFFFF8
=> binary: 1111_1111_1111_1111_1111_1111_1111_1000
0x1C AND 0xFFFFFFF8
yields:
0x18
(binary: 00011000), which is 24 in decimal.
sbrk
with either 8192 or the size previously calculated after the alignment - whichever is greater. Typecast the memory returned by sbrk to a pointer to your chunk struct
and set the fields accordingly. Then, insert this whole chunk of memory into the free list.void*
pointer to the start of the memory block allocated for the user. This address is calculated as (start_chunk_address + new_chunk_size + 8 bytes of bookkeeping). It's important to note that the usable memory for the user begins 8 bytes after the start of the allocated chunk.
This offset accounts for the bookkeeping information stored at the beginning of each allocated block. Therefore, the actual usable memory address provided to the user will be (start_chunk_address + new_chunk_size + 8 bytes).Consider Plank's implementation of the delete operation in a doubly linked list:
void dll_delete_node(Dllist node) /* Deletes an arbitrary item */
{
node->flink->blink = node->blink;
node->blink->flink = node->flink;
free(node);
}
NOTE: In our custom memory allocator, DO NOT free()
the node after readjusting the links. This is because we are only removing the node from our free list, and we want to keep the memory address valid for future allocation by my_malloc()
. The memory will be returned to the user, not returned to the system.
address_current_chunk + size_current_chunk == address_next_chunk
, add the size of the next chunk to the size of the current chunk and delete the next node. Otherwise, go to the next chunk in the list.