Dynamic memory
malloc takes memory from the heap at run time and free returns it. Every call can fail, leaks add up, and repeated allocation of different sizes fragments a small heap. Much firmware therefore allocates statically, or uses fixed-size block pools that cannot fragment and fail predictably.
malloc and free
malloc(n) returns a pointer to n bytes from the heap, or NULL when it can't find them, so every result must be checked. The memory is uninitialised, and it stays reserved until free returns it. Forgetting free is a leak; using a pointer after free, or freeing it twice, is undefined behaviour.
Why firmware is wary
A device may run for years without a restart. Allocating and freeing blocks of different sizes leaves gaps: the heap can have enough free bytes in total but no single gap large enough, so a later malloc fails, perhaps weeks after deployment. The time an allocation takes can vary too, and the standard library allocator is often not safe to call from an interrupt. Many coding standards for safety-related firmware therefore forbid dynamic allocation, or allow it only during initialisation.
Static pools
A pool hands out blocks of one fixed size from an array reserved at build time. Any free block fits any request, so it cannot fragment, both operations take a bounded time, and the worst case is visible in the source. When the pool is empty the call returns NULL, a failure that is easy to test.
#include <stddef.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#define BLOCK_SIZE 32#define BLOCK_COUNT 4static _Alignas(max_align_t) uint8_t pool[BLOCK_COUNT][BLOCK_SIZE];static uint8_t used[BLOCK_COUNT];static void *pool_alloc(void) { for (size_t i = 0; i < BLOCK_COUNT; i++) { if (!used[i]) { used[i] = 1; return pool[i]; } } return NULL; /* full: the caller must handle it */}static void pool_free(void *p) { for (size_t i = 0; i < BLOCK_COUNT; i++) { if (p == pool[i]) { used[i] = 0; return; } }}int main(void) { char *msg = malloc(16); if (msg == NULL) { return 1; } snprintf(msg, 16, "heap ok"); puts(msg); free(msg); void *blocks[4]; for (int i = 0; i < 4; i++) { blocks[i] = pool_alloc(); } printf("5th block: %s\n", pool_alloc() == NULL ? "NULL (pool full)" : "got one"); pool_free(blocks[1]); printf("after free: %s\n", pool_alloc() == blocks[1] ? "block 1 reused" : "other"); return 0;}
heap ok 5th block: NULL (pool full) after free: block 1 reused