I’ll focus on glibc ptmalloc on linux.



There is an important distinction to be made between the requested size, the usable size, and the total chunk size of an allocation.


When you call malloc, it will return some amount of memory greater than or equal to the requested size, for alignment/other reasons. This is the usable size.


The usable size seems to be 24 at a minimum, otherwise the requested size rounded up to the next value such that it is equal to 8 modulo 16.


The chunk size seems to be equal to the usable size + 8. But how can this be, if malloc uses 16 bytes of metadata?


The first 8 bytes of metadata is stored right before the usable memory, and contains the total chunk size of the current chunk (5 bits) and 3 internal flags (3 bits). This is included as part of the chunk.


The other 8 bytes of metadata is stored right after the usable memory, and contains prev_size, the size of the prior chunk. This is technically a part of the next chunk, a clever optimisation.



<----------------------------------chunk size-------------------------------------------> <-------next chunk------- ...
                       <----------------------------usable size------------------------->

|---------------------|------------------------------------------------------------------|-------------------|
|      xxxxx AMP      |                            usable memory                         |     prev_size     |
|      size  flags    |                                                                  |                   |
|---------------------|------------------------------------------------------------------|-------------------|


#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

int main() {
    int requested_size = 25; 
    void *ptr = malloc(25);

    size_t usable_size = malloc_usable_size(ptr);
    printf("Usable size: %zu bytes\n", usable_size);

    size_t chunk_size = usable_size + 8;
    printf("Chunk size: %zu bytes\n", chunk_size);

    size_t *header_ptr = (size_t *)((char *)ptr - 8);
    // The lower 3 bits of the size field are used for internal flags (A, M, P).
    chunk_size = *header_ptr & 0b11111000;
    printf("Chunk size read from metadata: %zu bytes\n", chunk_size);
    


    free(ptr);
    return 0;
}





Internal metadata flags:

P (PREV_INUSE): Previous chunk is allocated (bit 0).

M (IS_MMAPPED): Chunk obtained via mmap (bit 1).

A (NON_MAIN_ARENA): Chunk belongs to a non-main thread arena (bit 2).