realloc() is evil. Don't use it. Don't try to grow things in place.
Use SegArr, as described in my most recent comment.
I disagree: the SegArr just shifts the complexity to different code.
That's what engineering is :-)
True. I currently just see realloc() as being the proper place of complexity for certain cases –– not for all, by any means.
At the moment I'm just trying to write a set of specialized allocators that can be used each for specific use cases. As you saw, I'm not trying to reinvent the wheel either; I've - at least for now - implemented well known allocators, such as linear (also called "arena") and pool allocators.
Right. I asked, because I'm problem-solving oriented, and wanted to know if there was a *particular* problem you were trying to solve; or just building tools for solving a general class of problems.
None of this is reinventing the wheel, in my opinion. I haven't found anything that discusses which types of allocators are best suited for what kind of use cases, except for in projects that are implementing their own –– and even then it tends to be on the order of "hey, I think *this* works better than what we have right now; here's the patch series".
And then you get to the question of how much effort is appropriate here, considering how well already existing Garbage Collectors perform? (I have no answer or opinion to that myself.)
The effort, actually - as you can guess - is not much about implementation. It took me just a couple hours at this point to get something working well. Sure it's just still a WIP. The effort is all about using allocation properly and choosing the "right" allocation patterns.
I was thinking of entire projects, actually; not just implementing the allocator or GC itself.
And I'm definitely not criticizing, just pointing out that at one end of the toolbox are the GC's, at the other end static allocations only, and a whole plethora of tools in the middle. I myself am very interested in the tools in the middle, and enjoy the discussion; just thought it was prudent to mention the GC end since the static end has already been mentioned.
I do believe I covered
reallocation patterns already: in the use cases I know, there is usually only one that does not have a final size yet, and it would be useful to allocate it such that growing it would be
cheap safe; with an API 'promise' that it will be either freed, or resized to a final size, using a separate call. It does not matter if reallocating it really is a malloc()+copy+free(), what matters is that any repeated reallocations of it won't cause undue defragmentation, useless holes in the heap.
Fixed size allocations are nice, because fragmentation is not an issue.
It is tempting to think that using a generic allocator at first, and just checking what kind of allocations it makes and choosing an optimized version dedicated to it shuld lead to optimum results, but fact is, like you and others have already mentioned, we the programmers choose the allocation patterns when we implement our code. For better results, we should know beforehand what kind of allocator we target.
As an example, let's say I'm writing a smart display appliance based on Teensy 4.x ($20) or NXP MIMXRT1062, running at up to 600 MHz, with 512k of tightly coupled RAM, another 512k on the MCU, let's say 8M of PSRAM, and graphics resources (icons et cetera) on an microSD card (FAT fs, SDIO, fast enough). I have three 'zones', of which the TCM is more or less performance critical (so much so that the hottest code is best copied to TCM), with the 512k being "fast", 8M "slower". And microSD cards have basically unpredictable latency. I should do what companies do, and just rely on the hardware being fast enough that even bare-assed software works for the users... but
I cannot. I don't think I would use a single allocator for the three RAM zones. I think I would first have to do some test cases to decide how to best use the zones, before doing any systems integration. The allocation patterns and allocator schemes would be critical for long-term stability; and really need to be designed in before writing the code that relies on them. It does *feel* a bit bass-ackwards to me, but if that's what stability and efficiency requires, then that's what I'd do.