Freeing is the whole reason for block headers. Today Free clears a block's allocated flag so the space can be handed out again - and a later Malloc reuses it. Adjacent free blocks are not merged yet; that is the next chapter.
Free an allocated block by clearing its flag, and confirm the space is reused by a later allocation.
To free a block, take the payload offset the caller was given, step back 8
bytes to the header, and rewrite the block’s boundary tags with the allocated flag
cleared. That is it - the block’s bytes are untouched, but the allocator now
considers the space available, and the next Malloc that walks the list can find
and reuse it.
Notice today’s heap ends with two adjacent free blocks that stay separate: freeing the first block next to an already-free second block does not combine them. That is a real limitation - a later request bigger than either piece but smaller than their sum would fail even though the space exists. Merging neighbouring free blocks (coalescing) is what the next chapter is about; first we give the allocator a proper linked list of free blocks to make all of this fast.
func (a *Allocator) Free(payload int) error {off := payload - 8 // step back over the headersize, _ := a.blockAt(off)a.putBlock(off, size, false) // clear the allocated flagreturn nil}// no merging of neighbours yet - two adjacent free blocks stay separate