To begin, we need to write a memory allocator, or as we will be calling it,
a malloc function. The simplest malloc implementations maintain a linked-list of
free blocks of memory that can be partitioned and given out as needed. When a
user requests a chunk of memory, a block of the right size is removed from the
free list and returned. If no blocks of the right size exist, either a block of
a larger size is partitioned into smaller blocks or more memory is requested
from the kernel. Freeing a chunk of memory simply adds it back to the free list.
Each chunk of memory in the free list begins with a header describing the
block. Our header will contain two fields, one indicating the size of the chunk
and the second pointing to the next free block of memory:
typedef struct header { unsigned int size; struct header *next; } header_t;
Using headers that are embedded in the memory we allocate is really the only
sensible way of doing this, but it has the added benefit of automatically
word-aligning the chunks, which is important.
Because we will need to keep track of the blocks of memory currently in use
as well as the blocks that are not, we will have a used list in addition to a
free list. Items will be added to the used list when they are removed from the
free list, and vice-versa.
We are almost ready to complete the first step and write our malloc
implementation. Before we do that, we first need to understand how to request
memory from the kernel.
Dynamically allocated memory resides in the so-called heap, a section memory
between the stack and the BSS (uninitialized data segment – all your global
variables that have the default value of zero). The heap starts at a low address
bordering the BSS and ends at the program break, which resides somewhere between
the BSS and the stack. Attempting to access any memory between the stack and the
break will cause an access violation (unless you access within the