Where the metadata sits, how much space is really used, and how free/delete work from just the pointer p.
Header offset = a fixed constant hard-coded in the allocator (e.g. 16 bytes). Not stored — the allocator's own code knows it. That's why free(p) can compute p - headerSize from just p.
Block size = how big this allocation is. This is stored, inside the header. free uses the fixed offset to find the header, then reads the block size from it.
malloc(4) reserves HEADER + 4 bytes + padding; returns p pointing at your data.free(p): go to p − headerSize (fixed offset), read the block size, return the whole chunk to the free list.new Entity() = operator new (usually calls malloc → same header layout) + runs the constructor on the object.delete p: runs the destructor first (on the object at p), THEN frees the chunk exactly like free (p − headerSize → read size → free list).new/delete just add ctor/dtor on top.new Entity[n] stores an extra count cookie (the value n) just before the first element, at a fixed compiler-defined offset.delete[] p: read n from p − cookieSize, run the destructor n times (elem n−1 … elem 0), THEN free the whole block.delete p here skips the count → wrong number of destructors + allocator confusion = undefined behaviour. That's why new[] needs delete[].Note: for simple types like int[] (no destructor) some compilers skip the count cookie — but for objects with destructors it's always there. Exact header/cookie sizes are allocator- and compiler-specific; the shapes above are the idea, not exact byte counts.
malloc ↔ free • new ↔ delete • new[] ↔ delete[]. Crossing them (e.g. free on a new pointer) skips the destructor and can mismatch allocators → corruption / UB.