Search code examples
cmemory-managementmallocheap-memoryfree

unexpected output of size allocated by malloc in C


I have read that malloc actually allocates (required_size + 1) blocks of memory and it stores the size in the first block and the pointer to the second block is returned. This way free() knows how much memory to free. So, I wrote a small code to output this size.

int *p = (int *)malloc(100*sizeof(int));
printf("size = %d\n",p[-1]);

Since I am allocating space for 100 ints, I am expecting the size to be 400. But the output was 409. For 50 int's output was 209 and for 1000 int's output was 4009. Can someone pls explain why the output is off by 9 bytes?


Solution

  • Assuming the implementation is glibc (or similar), the following can be found in comments in malloc.c:

    Minimum overhead per allocated chunk:   4 or 8 bytes
       Each malloced chunk has a hidden word of overhead holding size
       and status information.
    
    Minimum allocated size: 4-byte ptrs:  16 bytes    (including 4 overhead)
              8-byte ptrs:  24/32 bytes (including, 4/8 overhead)
    
       When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte
       ptrs but 4 byte size) or 24 (for 8/8) additional bytes are
       needed; 4 (8) for a trailing size field and 8 (16) bytes for
       free list pointers. Thus, the minimum allocatable size is
       16/24/32 bytes.
    

    That explains the existence of overhead.

    Now, for the 'off by 1', the flags are responsible for that. Since sizes (actually) allocated by malloc() will be always multiples of 8, the three least significant bits are used to store flags:

    /* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
    #define PREV_INUSE 0x1
    
    /* extract inuse bit of previous chunk */
    #define prev_inuse(p)       ((p)->size & PREV_INUSE)
    
    
    /* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
    #define IS_MMAPPED 0x2
    
    /* check for mmap()'ed chunk */
    #define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)
    
    
    /* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
       from a non-main arena.  This is only set immediately before handing
       the chunk to the user, if necessary.  */
    #define NON_MAIN_ARENA 0x4
    
    /* check for chunk from non-main arena */
    #define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
    

    Edit: ah, and I'd almost forgot. The size is stored as size_t, not an int, so you should use that type to access it.