Memory Allocators
While malloc is the most common way to allocate memory, there are multiple
other use-cases where we may have very specific allocation patterns. Some of
these patterns can make sub-optimal uses of malloc or be much more
complicated than expected due to memory management issues. For these use-cases,
the lib-common provides several allocators.
p_new()
We do not use malloc directly, instead we use wrappers of the p_* family.
Our wrappers have the following properties:
-
they are type safe
-
they set the memory to 0 by default (use the
*_rawvariants to avoid the cost ofmemset) -
deallocations are idempotent: calling
p_delete()twice on the same location is safe sincep_delete()sets the location to NULL.
mem_fifo_pool
The mem_fifo_pool is a memory pool optimized for the case where the first
allocated block is also the first to be released. The typical use case is a
queue of messages where we usually answer the first message before processing
the next one. The mem_fifo_pool does not enforce the de-allocation order but
is optimal for "FIFO" case.
mem_stack_pool
The mem_stack_pool works the same way the program stack works: it works with
nested frames. All the allocations made within a frame are freed when we leave
it. That means the workflow will look like:
-
mem_stack_push(mp): start a new frame 1-
mp_new(mp, type, count): do an allocation 1 in the frame 1 -
mp_new(…): do another allocation 2 in the frame 1 -
mem_stack_push(mp): start a new frame 2-
mp_new(): do an allocation 3 in frame 2 -
mem_stack_pop(mp)@: leave frame 2, free the allocation 3
-
-
mp_new(…): do an allocation 4 in frame 1 -
mem_stack_pop(mp): leave frame 1, free allocations 1, 2 and 4
-
Note: You must be careful of reallocations, though. In our example, allocation 1 belongs to frame 1 and cannot be reallocated in frame 2.
t_stack
The most common use of the mem_stack_pool is by using the globally defined
t_pool(). The t_pool() is a unique thread-local mem_stack_pool that can
be used from anywhere in your program. It is usually used as a complement for
the stack to perform dynamic allocations whose lifetime is limited to a
function or a block within that function. It avoids stressing malloc with
frequent (usually small) allocations, avoids de-allocation code (and thus avoids
lots of cleanup code and memory leaks).
In order to work with the t_stack we have several helpers (the t_* macros):
-
t_scope: declare a frame on the t_stack. The frame is opened on the line of thet_scopeand is closed when we leave the scope in which thet_scopelives -
t_new,t_new_raw,t_realloc…: allocate or reallocate data in the current frame
void foo(void)
{
t_scope; /* enter frame 1 */
void *a1 = t_new(...);
void *a2 = t_new(...);
{
t_scope; /* enter frame 2 */
void *a3 = t_new(...);
} /* Here we're leaving frame 2 */
void *a4 = t_new(...);
} /* Here we're leaving frame 1 */
Note: As a consequence of the reallocation limitation of the mem_stack_pool,
using t_realloc on an allocation of frame 1 in frame 2 will fail (in practice
it will abort the execution of the program). This is also true for string
buffers or vectors allocated on the t_stack that could grow in a nested
frame. Using t_stack allocated buffers/vector is very convenient because
there is no need to explicitly de-allocate them, but is risky due to this
reallocation limitation.