Before it hands anything out, the allocator initializes its arena as a single free block spanning the whole heap. Today NewAllocator lays down that first block, the raw material every allocation is carved from.
Initialize the arena as one free block covering the entire heap.
An allocator does not start empty - it starts with all of its memory in one
giant free block. Allocating is then a matter of carving pieces off that block
(and later, off the free blocks that carving produces). So NewAllocator writes a
single free boundary-tagged block covering offsets [0, size): a header at 0 and a
footer at size - 8, both encoding the full size with the allocated flag clear.
Reading the block at offset 0 back gives the whole heap as one free region. This
is the initial state every later operation transforms: Malloc will find this
block and split a piece off it, Free will hand pieces back, and coalescing will
eventually merge everything back into one block just like this. Keep a note of the
heap’s start (0) and end (size) - walking between them is the next lesson.
func NewAllocator(size int) *Allocator {a := &Allocator{buf: make([]byte, size)}// the whole arena begins as a single free blocka.putBlock(0, size, false)return a}// helper to read a block's (size, alloc) from its header at offfunc (a *Allocator) blockAt(off int) (int, bool) { /* decode header */ }