Why malloc always does more than I asked for?

(ssenthilnathan3.github.io)

33 points | by nathaah3 2 days ago

9 comments

  • CodesInChaos 2 hours ago
    Your bump allocator suffers from integer overflows turned into buffer overflows when the requested allocation is big enough:

      if (a->cursor + size > a->limit) return NULL; // out of memory
    
    I'd rewrite it like this:

      if (size > a->limit - a->cursor) return NULL; // out of memory
    • matheusmoreira 20 minutes ago
      I used the compiler's __builtin_add_overflow in order to deal with that issue in my memory allocator. At this point I'm probably on track to replace every arithmetic operator in the entire codebase with those things.
    • dn3500 1 hour ago
      Your test is backwards. I would write it like this:

        if (size > a->limit - a->cursor) return NULL;
  • pjmlp 50 minutes ago
    Old magazines like The C/C++ Users' Journal and DDJ used to have ads for companies selling malloc()/free() replacement libraries, exactly because a single implementation isn't adequate to all scenarios.
  • phire 2 hours ago
    > so alloc() doesn’t just need to hand back a pointer. it needs to hand back a pointer that’s correctly aligned for whatever type the caller is about to store there.

    Malloc doesn't know the required alignment (because has no idea what the type is, everything is cast through void). So all malloc implementations have a minimum alignment guarantee. Typically 16 bytes these days on x86, as that means even 128bit SSE values will end up aligned by default.

    You couldn't go below the sizeof(void ) anyway, the backpointer needs to aligned too.

    The padding only happens when you use memalign or aligned_malloc to specify a much larger alignment.

    • CodesInChaos 2 hours ago
      There is no reason an allocation needs to contain any inline metadata. And even if it does, the allocator could choose to make it unalined, and pay the cost of an unalined access on de-allocation.
      • phire 2 hours ago
        True.

        But most C code out there assumes malloc will always return something that is at least aligned to sizeof(void *), it's very rare to see aligned_alloc. So how is your alloc allocation going to know when it can get away with a smaller alignment?

        Even if you are on a cpu that doesn't fault on unaligned memory access, any malloc implementation that doesn't align by default will have serious disadvantages in any benchmarks. IMO, There is no good reason to use an unaligned backpointer.

        • CodesInChaos 58 minutes ago
          Yes, large allocations need to be aligned to `alignof(max_align_t)`. But small allocations could have a smaller alignment. For example a single byte allocation can actually be a single byte with no alignment, since types can't have an alignment larger than their size.
      • adrian_b 11 minutes ago
        This is explained in TFA, where it is mentioned that you can replace inline metadata with a pointer to metadata (or an index), which may be unaligned, if necessary.

        However, the pointer to metadata is not really necessary.

        The associated metadata could be stored in a table, and the index of the metadata could be computed from the offset of the pointer returned by malloc to the start of the heap (possibly using a hash function).

  • ncruces 32 minutes ago
    I wrote this bump malloc that I use for very short lived Wasm modules: https://github.com/ncruces/wasm2go/blob/main/libc-gen/c/mall...
  • drivebyhooting 2 hours ago
    Why bother with dynamic padding and a back pointer? That wastes at least 8 bytes.

    You might as well always align to 8 bytes and make your header a multiple of 8.

    • jraph 59 minutes ago
      The back pointer could also be a 1 byte number giving the size of the padding.
  • lexicality 1 hour ago
    I'm a little confused. We start with

      [ Header ][ ...variable padding... ][ Back Pointer ][ User Memory ]
                                           ^ always exactly sizeof(void*)
                                             bytes before User Memory,
                                             no matter how much padding
                                             came before it
    
    and then it says we don't need to align the back pointer and we end up with

      [ Header ][ Back Pointer ][ Padding ][ User Memory ]
    
    without a clear explanation of how we now get to the back pointer if it's behind the variable alignment.
    • torh 1 hour ago
      This puzzled me as well.
  • irenaeus 2 days ago
    I've read some allocator walkthroughs before but I thought that this line stood out:

    "to get individual free() working, the allocator needs to remember something about every allocation it handed out. and that’s the moment metadata stops being optional."

    That's just a very nice distillation of an important concept.

    • HexDecOctBin 2 hours ago
      This becomes particularly important when the allocation and metadata cannot (or should not) be stored in the same address space. An example of this is allocating memory on GPUs.

      The conventional approach for allocating memory on GPUs for games and other applications is to use a real-time allocator such as TLSF. However, it is not usually discussed that TLSF is real-time because it stores metadata in-band. It is possible to create a variant of TLSF that preserves its real-time properties while storing metadata out-of-band, but this requires careful consideration.

    • geocar 2 hours ago
      Oh? How do you think munmap does it?

      If you can convince the caller to keep track of that metadata themselves you obviously don’t need to. That can be important.

      Something I noticed is that _very often_ the code that is calling malloc(n) is keeping track of n somehow for its own reasons (bounds checking, grow/gap pointers, etc) so merging the value halves stack churn and it’s an easy win.

      • xxs 54 minutes ago
        > keep track of that metadata themselves you obviously don’t need to

        likely it'd be perf. hit in most cases. They'd have to copy to the tail end (likely) of the allocated area. Or the start and offset the pointer, they'd need to know the size of the metadata and account for that, including aligning it.Hence, the tail feels 'nicer'

        It's possible to manually use mmap and forgo malloc entirely, rolling your own arena manager.

      • byroot 2 hours ago
        That's why C23 introduce free_sized [0].

        [0] https://en.cppreference.com/c/memory/free_sized

    • lifthrasiir 2 hours ago
      In the other words, free() is a flawed API. :-)
    • nathaah3 2 days ago
      glad you found it useful :)
  • iLoveOncall 15 minutes ago
    Having to recode malloc and free from "scratch" in school was a great teaching experience and there's not a month since where I don't encounter something that I understand better thanks to that exercise.