On 22/01/2024 10:22, Richard Kettlewell wrote:
> bart <
b...@freeuk.com> writes:
>> malloc has sort of created a rod for its own back by needing to store
>> the size of the allocation. That will take up some space even when
>> malloc(0) is called, if NULL is not being returned.
>
> The alternative is making a rod for the back of every single caller
> (i.e. all consumers must track allocation sizes).
Not at all. Let's first emulate a pair of functions where the caller is
expected to remember the size:
void* malloc_s (size_t n) {return malloc(n);}
void free_s (void* p, size_t n) {free(p);}
Then a typical alloc/dealloc sequence might look like this:
typedef struct {int d,m,y;}Date;
Date* p;
p=malloc_s(sizeof(*p));
...
free_s(p, sizeof(*p));
Is that particularly onerous? If you have a fixed-size object like a
struct, then you will always know its size.
For variable-sized objects, then yes you need to keep a record of the
size, but the chances are you have to do that anyway. For example to be
able to iterate over that dynamic array.
But if you really wanted (for example when allocating variable length,
zero-terminated strings), you can write a couple of wrappers around
malloc_s and free_s to emulate what malloc and free provide in terms of
not needing to remember the allocation size:
typedef long long int i64;
void* malloc2(i64 n) {
void* p;
p=malloc_s(n+sizeof(i64));
if (p==NULL) return NULL;
*((i64*)p) = n;
return (char*)p+sizeof(i64);
}
void free2(void* p) {
i64* q = (i64*)((char*)p-sizeof(i64));
i64 size = *q;
free_s(q, *q);
}
Untested code, it's to demonstrate what's involved: you ask for an
allocation 8 bytes bigger, use the 8 bytes at the beginning to store the
size, and return a pointer to just after those 8 bytes. (I'm assuming
8-byte alignment will suffice.)
Now you can do this:
p=malloc2(sizeof(*p));
...
free2(p);
> I think the design of malloc got this one right.
I think it got it wrong. Now everyone is lumbered with an allocation
scheme that will ALWAYS have to store the size of that struct, perhaps
taking as much space as the struct itself.
Imagine allocating 100M of those structs, and also storing 100M copies
of sizeof(Date) or whatever metadata is needed.
Getting around that, by writing your own small-object allocator on top
of malloc, is a lot harder that adding your own size-tracking on top of
a malloc that does not store any extra data. As I've shown.
(This is also a scheme where, if a user needs to get the size of an
allocation block, it can do so:
i64 size_s(void* p) {
i64* q = (i64*)((char*)p-sizeof(i64));
return *q;
}
But this will be the requested size not the capacity of the allocated
block. That would depend on how malloc_s/free_s are implemented.)