A block records its size at both ends - a header at the front and an identical footer at the back. Today you write these boundary tags into the arena so a block is delimited from both directions, which pays off later when merging free neighbours.
Write a block's header and matching footer into the arena, and read both back.
A header at the front of a block tells you its size, which lets you step forward to the next block. But freeing well also means merging with the free block before you, and for that you need to find where the previous block starts - stepping backward. The standard solution is a boundary tag: write the same size-and-flag word as a footer in the block’s last 8 bytes, mirroring the header.
Today the header and footer are just two identical words a fixed distance apart:
for a block of size 32 at offset 0, the header sits at 0 and the footer at
0 + 32 - 8 = 24. Every block from here on carries both, so the overhead per block
is 16 bytes (8 header + 8 footer). Writing the footer now, before anything reads
it, keeps the layout uniform; the payoff - reading a previous block’s footer to
coalesce backward in constant time - arrives in a couple of lessons.
type Allocator struct { buf []byte }func NewAllocator(size int) *Allocator { return &Allocator{buf: make([]byte, size)} }// header at off; footer in the block's last 8 bytesfunc (a *Allocator) putBlock(off, size int, alloc bool) {h := pack(size, alloc)putU64(a.buf, off, h) // headerputU64(a.buf, off+size-8, h) // footer mirrors it}// putU64/u64 read and write an 8-byte little-endian word in buf