I was looking at zalloc and realized that using zig allocators in c code would be extremely useful. What if when translating c code to zig, translate-c optionally replaced all c allocations with a zig allocator and added the allocator to the function parameters. If a c function calls another c function it could pass it the allocator. A major benefit of this is it would allow leak checking for c programs with DebugAllocator.
Simple example:
void example() {
int *ptr = (int *)malloc(sizeof(int));
free(ptr);
}
This seems useful for such a small snippet, but easily runs into edge cases that would make this quite hard.
First Zig Allocators expect a slice(ptr+len) to free or realloc memory, while they in C just need a pointer because the allocator implementation stores the length internally. This would change the layout of a lot of structs and slices, which makes this hard.
Secondly C doesn’t have a standard way to do error propagation. Some use NULL, others errno, and others negative/enum return codes. Often you also see some cleanup via goto. How would translate-c map error.OutOfMemory to that?
Thirdly imagine a struct with a function pointer like this
struct Handler {
void (*callback)(int);
};
If implementation A allocates, it now needs an Allocator parameter. If implementation B does not it doesn’t. You can’t assign both to the same function pointer anymore because their signatures differ.
Fourthly, compatibility with precompiled object files, but this minor.
malloc and zigs allocators have different contracts;
most notably in zig you are expected to keep track of the allocation size, but malloc does so itself internal.
malloc also align allocation to 16, but zig uses the type’s natural alignment, or you can override it.
For simple things this doesn’t matter, but more complex allocated data structures are absolutely affected by this.
C libraries often have wrappers for dealing with common allocations, they also often have a thin wrapper macro that can be overridden to use a different allocator.
Didn’t know that. Is this really enforced or more of a usually type thing?
Because if it is enforced this would seem quite wasteful and would lead - so I imagine - to unnecessary fragmentation if one allocates something smaller than 16 bytes.
malloc is a general purpose allocation function, it is also old.
Since it only takes a size, it has to make some assumptions about alignment, and to stay general purpose it has to cater to the worst. 16 is a very safe assumption for alignment, it is totally possible it is a little smarter, taking into account the size. But AFAIK, it uses an alignment of 16.
There is an aligned_alloc function that should definitely be preferred for non-trivial cases.
The malloc alignment is extremely useful once you want to use SIMD vectors (at least it was before aligned_alloc() was added to C11). Not sure if it has changed on recent CPU models, but trying to load/store a SIMD vector would crash if not at least 16-byte aligned.
to unnecessary fragmentation if one allocates something smaller than 16 bytes
That’s why one shouldn’t do such small allocations in the first place Usually there’s also housekeeping data associated with each allocation, and the smaller the allocation, the bigger the relative waste.
As of Daniel Lemire it seems like that at least on some modern processors unaligned SIMD isn’t to bad. But he just has one small sentence at the botton on the topic.
Yeah, it’s been a while since I last stumbled over that problem. IIRC on 32-bit Windows, MSVC’s malloc is 8-byte aligned and that crashed SSE2(?) SIMD vector memory load/stores, and of course it also didn’t have any builtin aligned-alloc alternative so one had to cobble together a custom aligned-allocator.