Consider the alternative: what else would you use the memory for?
If nothing, then it doesn't matter how you allocate it; fill a whole bunch extra with gibberish for all that matters!
Or, if you have a number of things to allocate, but need to fit the maximum of all of them, in the worst case condition: do that, static allocations of maximums.
If you can't fit the maximum in the worst case, and can't upgrade to a bigger MCU: you will have to restrict how much can be allocated, in whatever combinations would overallocate. For example, maybe you can only have 3 or 4 pipes open, while also doing -- I don't know, an adjustable (in this case unusually long) audio buffer or something?
Put another way: don't be lazy.
malloc() lets you be lazy, in certain ways; and you can get away with it a lot of the time on the PC. Even ignoring the return (error) value because it "basically" never happens.
Force yourself to think about what resources you will need in the application, and divide them up suitably.
This also leads to the thought process: "what if I do use
malloc()?" Well, you can put whatever you want there [on the heap], and obviously, be responsible about allocating and freeing (and not use-after-free) objects, but since you will be resource constrained (i.e., given the above assumption that maximum static allocations won't fit), then you must use the error condition (out of memory) to restrict further allocations, assigning priority (i.e., which things you allocate first) as needed.
And mind you may have to deal with fragmentation. Something like a radio, it's probably fine to just shut the whole thing down for a few milliseconds, while setting up buffers and everything. You could have an array of allocated objects (just pointer arrays, fairly compact) and just go through and dump them all (
free()), then allocate the new set. No memory leaks, nothing is ever left hanging. (Or even re-init the allocator itself, literally forgetting about what allocations were assigned.) Whereas if the allocations need to be persistent and mutable (sometimes you add/remove a pipe here or there, other times you adjust the buffers, or load some files, or..), you may need quite a lot more memory than one would naively assume, due to fragmentation costing allocation efficiency.
malloc() is a rather heavy-weight solution for an otherwise-static problem, anyway; mind that you might not use it at all, as such, but your own handy lightweight allocator instead. For example, if you have two data types to allocate, you might allocate a
char array or whatever for total size (as your not-heap), and just place objects into it starting from the bottom and top ends respectively, making sure not to overlap objects once it reaches capacity.
Or not even use separate types, but a union of both (or a superclass, since this is C++), and just ignore the wasted space / padding in the smaller type. The advantage is, by allocating objects of maximal size, you can free and reassign slots arbitrarily, with no worry of fragmentation. Or rather, the fragmentation is there, as you're always wasting the padding amount, but because everything stays organized (fixed size), it will never spiral out of control as general-purpose allocation can. Obviously, this is most reasonable when the objects have similar sizes -- maybe you need 50 bytes for a pipe, 36 bytes for a file handle, etc.; whereas, you wouldn't be able to (or, want to, at least) allocate up-to-2048 byte buffers alongside those same objects, say. You might want to solve such problems by using separate pools instead -- which is to say, you can always mix and match static and simple-allocator and general-
malloc() methods, as long as you keep track of them all properly.
Disclaimer: this isn't much of an EE topic, this would be better placed on CS or Overflow. And I'm just a humble C programmer, I rarely use anything more than static as it is. (Feel free to comment or edit this post for correctness.)