The memory-allocation functions in C allow blocks to be allocated and freed in any order, with the caveat that fragmentation may occur.
In many embedded systems, this is not necessary and it is sufficient to unallocate memory in the reverse order allocated (or in some cases just allocate memory and never deallocate it at all until system reinitialization).
A good approach for handling this is to simply keep a pointer to the last address allocated. When you receive a request for more allocation, copy and bump the pointer.
Depending upon your application, it may be better to allocate memory bottom-up, top-down, or both.
Code:
unsigned char *botptr;
unsigned char *topptr;
#define MEM_SIZE 64
unsigned char allocmem[MEM_SIZE];
#define mem_init() \
(botptr = allocmem, topptr = allocmem+MEM_SIZE)
#define bot_alloc(size) \
(void*)(botptr += (size), botptr-(size))
#define top_alloc(size) \
(void*)(topptr -= (size))
#define bot_free(what) \
(botptr=(char*)(what))
#define top_free(what,size) \
(topptr = (char*)(what)+(size))
#define top_keep(what) \
(topptr = (char*)(what))
Note that the code as given does not do any error-checking (to ensure that topptr remains above botptr). Bot_free() will release the indicated object and everything allocated after it by bot_alloc(). Top_free() will do likewise for stuff allocated by top_alloc(), but you must supply the size of the item. Top_keep will not free the indicated item, but will free everything allocated after it by top_alloc().
Note that regardless of the number of items allocated, there is no overhead other than the top and bottom pointers.