Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Comments on Embedded C - memory pool types
Parent
Embedded C - memory pool types
In embedded systems, dynamic memory allocation is discouraged (or forbidden) due to various reasons.
A better idea is to statically allocate a chunk of memory whose size is known at compile time - this is called a memory pool. Then, during run time you can partition that chunk into smaller blocks and "hand it out" to functions or processes that need it. An example of a memory pool is blatantly stolen from one of Lundin's answers from the past (I can't seem to find the link)
#define MAXSIZE 100
static uint8_t mempool[MAXSIZE];
static size_t mempool_size = 0;
void alloc_init(void)
{
mempool_size = 0;
}
void* static_alloc(size_t size)
{
uint8_t* result;
if(mempool_size + size > MAXSIZE)
{
return NULL;
}
result = &mempool[mempool_size];
mempool_size += size;
return result;
}
size_t alloc_get_size(void)
{
return mempool_size;
}
This is apparently known as an arena allocator. It is simple to understand and to use.
But one disadvantage is that if I have to "free" memory from processes that have finished using their memory block I need to "free" the entire memory pool at once by calling alloc_init(). In other words, I cannot deallocate parts of the memory pool, I can only deallocate the entire thing at once. This becomes a problem in some situations, because it means I have to wait for all users of the memory pool to finish their task before I can deallocate memory and hand it over to the next process.
What are some other memory pool architectures/types than this arena type?
Post
The TL;DR is: use the correct tool for the task. Dynamic memory allocation in the context of embedded microcontroller system is indeed pretty much never the correct tool, for a long list of reasons.
Ask yourself what your actual task is - what is the reason you need dynamic memory? If the answer is something like "I need a shared memory pool" or "I don't know the memory required at compile-time", that's the wrong answer - why do you need a shared memory pool, why don't you know the upper limit of what your program is supposed to be doing? Keep asking why until your answer lands in the specific project requirements. Or if it doesn't land there, then either the requirements are insufficient and should be revisited. Or it could turn out that you are doing some meta task unrelated to the actual project, in which case you should abandon it and get back on track.
I believe that the code in the question comes from Static allocation of opaque data types and it's important to understand that context. If you don't have any opaque types simply declare the object as a plain static file scope variable and don't worry about memory pools.
In the specific scenario when you have an opaque type, achieved by forward declaring an incomplete struct, the application code simply cannot allocate an instance of that object since the struct implementation and size is unknown to the application. Therefore you have two options:
- The opaque type class allocates memory for every object of that type internally, through a constructor-like function, or
- The caller allocates memory in some global memory pool by asking the opaque type class how large one object is, through some manner of "get_size()" API.
For simple applications the former is likely preferred as it makes things less complex. The latter is only preferable when you have a lot of opaque types and you want their memory allocated together (for example for data cache reasons).
Notably one has to be aware that these memory pools that work on a character type array do declare an array with "effective uint8_t type". If we let a struct point into such an array and that struct does not have a similar uint8_t array among its members, then dereferencing the struct will explicitly invoke undefined behavior as per "strict aliasing". There are two ways to avoid it: either don't use a struct but a union between the struct type and a uint8_t array of the same size as the struct. Or simply disable strict aliasing optimizations, which were only ever a problem with the gcc compiler specifically - the option is gcc -fno-strict-aliasing.
But one disadvantage is that if I have to "free" memory from processes that have finished using their memory block
This doesn't make sense in embedded MCU systems and where most people considering dynamic allocation go wrong. The scenario "I don't know how much memory I need at compile-time" does not exist. A microcontroller system, or any high reliability system for that matter, must be fully deterministic and there can be no unknowns. From Why should I not use dynamic memory allocation in embedded systems?:
"Saving memory" and freeing doesn't make sense
Calling free() in a single core microcontroller application never makes any sense - there is nobody to share the memory with, our program has complete control of it all. As established earlier, we need to handle the worst case scenario so we need to allocate that exact amount. Freeing up memory when we are not executing the worst case scenario is senseless, because if there are parts of the code which would actually benefit from that extra available memory, that only means that those very same parts will either perform poorly or fail/crash during the worst case scenario, so that would be a design mistake.
Your requirements and application use-cases must cover all situations including the worst-case ones. Your application must work just as fine in the worst-case scenario as in any other scenario, so you know in advance how much memory you need: exactly as much as is necessary for the worst-case scenario. Not more, not less - you need that exact amount. There is nothing "dynamic" about it.
The need for dynamic memory is typically just one big "XY problem". You think you need solution "dynamic memory" to solve problem X, and so you are looking for various alternatives to dynamic memory when it's likely the wrong solution to your original problem to begin with.
So consider: when exactly will any part of your program be "finished" with a memory block and how does that even make sense from your requirements' and program design's point of view? Did you pick a MCU with insufficient RAM for the project requirements or what? All explanations from there on enters the realm of dirty patches and ad hoc solutions, when we have thrown all good programming practices out the window anyway.
If your processes have to wait for each other to finish just because there is too little memory, then clearly you have picked the wrong MCU to host the RTOS. And why are individual processes even mucking around with a chunk of shared memory instead of using their local process stack for it? (Ie the RTOS' equivalent to thread_local.) At this point we need to stop and question what we are even doing, because our program design is apparently all over the place and maybe we've over-engineered the whole project.
What are some other memory pool architectures/types than this arena type?
There are various even more specialized types and which ones that apply might matter if the data is read/write or read-only.
A linked list implemented on top of a static array with array indexes instead of pointers for example, which can be a handy type for implementing queues or even certain wear leveling algorithms.
Hash tables could make sense if you are dealing with large amounts of data and need more or less constant look-up time. These make most sense when the data is read-only. Same with binary trees, expression parsing trees etc etc.
They key is specialized use, just as the memory pool in the question is a container for specialized use.

0 comment threads