I agree on some level, but I think if you’re intentional with your design you can get by with a very simple arena allocator where an allocation is a single subtraction from a pointer.
In my experience, most programs have a few long-lived data structures that can be initialized up front, and then lots of small data structures that are created and destroyed frequently. If you do it right, you can put the long-lived guys at the beginning of the arena and then just wiggle your arena head pointer back and forth for the small guys.
This does take some more thought and consideration, and is definitely a bit trickier than just using malloc when you need it, but for me the trade off is worth it (for one I gain some amount of satisfaction from imagining my data all neatly in a row).
I use a double-ended stack allocator for most allocations in personal C projects. The second end adds a significant amount of flexibility; temporary allocations can accumulate at one end of the stack without preventing a longer-lasting allocation from being made later (yet before the temporary allocations can be freed) on the other end.
In my experience, most programs have a few long-lived data structures that can be initialized up front, and then lots of small data structures that are created and destroyed frequently. If you do it right, you can put the long-lived guys at the beginning of the arena and then just wiggle your arena head pointer back and forth for the small guys.
This does take some more thought and consideration, and is definitely a bit trickier than just using malloc when you need it, but for me the trade off is worth it (for one I gain some amount of satisfaction from imagining my data all neatly in a row).