I wrote this snippet to allocate blocks of memory, whose sizes and number are available during initialisation. I choose to equally divide the statically allocated memory. There are some error checks before allocating a block to a pool of memory and I maintain a free list in the memory blocks. It is not thread safe at the moment. Can I get some feedback?
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#define MAX_POOLS 4 // ceiling for num of pools
#define HEAP_SIZE 65536 // input heap size
// simple linked list
typedef struct {
void* next;
} node_t;
typedef struct {
uint16_t max_blocks; // max allowed blocks
uint16_t blocks_alloced; // contiguous blocks allocated
uint8_t* pool_start; //starting address of the pool
node_t* head; // free list pointer
size_t block_size; // block size
} pool_allocator_t;
static uint8_t pool_heap[HEAP_SIZE];
pool_allocator_t g_pools[MAX_POOLS];
static inline bool is_power_of_two(size_t in);
// added for tests
void pool_deinit(void)
{
memset(pool_heap, 0, HEAP_SIZE);
memset(g_pools, 0, MAX_POOLS);
}
bool pool_init(size_t* block_sizes, size_t block_size_count)
{
// not supported
if (block_size_count > MAX_POOLS) {
return false;
}
// non positive inputs are invalid
for (int i = 0; i < block_size_count; ++i) if ((int64_t)(block_sizes[i]) <= 0) return false;
size_t unalloc_space = sizeof(pool_heap);
uint8_t* boundary_start = pool_heap;
uint8_t* boundary_end = pool_heap + sizeof(pool_heap);
memset(g_pools, 0, MAX_POOLS * sizeof(pool_allocator_t));
uint8_t* base_address = boundary_start;
// equally sized partitions
uint16_t partition_size = unalloc_space / block_size_count;
for (int i = 0; i < block_size_count; ++i) {
if (!is_power_of_two(block_sizes[i]) // enforce power of two numbers only, doesn't exactly take care of alignment issues
|| block_sizes[i] > partition_size // bigger than partition?
|| base_address > boundary_end // did we overstep?
|| base_address + block_sizes[i] > boundary_end) { // is expected size not going to be fulfilled?
// clean up
memset(g_pools, 0, MAX_POOLS * sizeof(pool_allocator_t));
// init failed. fails even if first few blocks have been allocated successfully
return false;
}
g_pools[i].pool_start = base_address;
g_pools[i].head = NULL;
g_pools[i].block_size = block_sizes[i];
g_pools[i].max_blocks = partition_size / block_sizes[i];
unalloc_space -= partition_size;
base_address += (g_pools[i].max_blocks * g_pools[i].block_size);
// print stats for this partition
}
return true;
}
void* pool_malloc(size_t n)
{
// non-positive numbers are invalid inputs
if ((int64_t)n <= 0) return NULL;
pool_allocator_t* pool = NULL;
// decide which partition will it go to
uint8_t valid_partition = UINT8_MAX;
size_t valid_block = UINT32_MAX;
for (int i = 0; i < MAX_POOLS; ++i) {
// get smallest block size out of all pools that can fit this input
if (g_pools[i].pool_start != NULL
&& n <= g_pools[i].block_size
&& g_pools[i].block_size <= valid_block) {
valid_block = g_pools[i].block_size;
valid_partition = i;
}
}
// valid partition was found
if (valid_partition != UINT8_MAX) {
pool = &g_pools[valid_partition];
}
// no relevant partition found for this size
if (pool == NULL) {
return NULL;
}
if (n <= pool->block_size) {
} else {
return NULL;
}
// result block pointer, start with NULL
node_t* ret_block = NULL;
// see if memory is already available before getting new block?
if (pool->head != NULL) {
// head is non-null, so we have a previously freed block for use
// get this block's address and update head
ret_block = pool->head;
pool->head = pool->head->next;
} else {
// head is null, all previously allocated blocks are still under use. get a new block
// check if space is available
if (pool->blocks_alloced < pool->max_blocks) {
// get address of new block, increment blocks under use
ret_block = (void *)(pool->pool_start + pool->blocks_alloced * pool->block_size);
pool->blocks_alloced++;
} else {
// cannot satisfy requirement, fail
}
}
return ret_block;
}
void pool_free(void* ptr)
{
// base case
if (ptr == NULL) {
return;
}
// check if it is beyond the boundaries of heap
if (ptr < (void*)pool_heap || ptr >= (void*)&pool_heap[HEAP_SIZE]) {
}
pool_allocator_t* pool = NULL;
uint8_t allocated_pools = 0;
for(int i = 0; i < MAX_POOLS; ++i) if (g_pools[i].pool_start != NULL) allocated_pools++;
// find which partition the memory exists in
for (int i = 0; i < allocated_pools; ++i) {
void* curr_pool_boundary = (void*)( g_pools[i].pool_start + g_pools[i].max_blocks * g_pools[i].block_size);
if (ptr >= (void*)g_pools[i].pool_start // within boundary of this pool
&& ptr < curr_pool_boundary // within boundary of this pool
&& ((uint8_t*)ptr - g_pools[i].pool_start) % g_pools[i].block_size == 0) { //check if it is pointing to a valid block
// found in this bin, check if it is pointing to a valid block
pool = &g_pools[i];
}
}
// memory block wasn't found, exit
if (pool == NULL) {
return;
}
// set to 0, may not be necessary
memset(ptr, 0, pool->block_size);
// now that it's de-allocated, we can make it a node
node_t* freed_mem = (node_t*)ptr;
// point next to previous free location
freed_mem->next = pool->head;
// last freed node should become head
pool->head = freed_mem;
}
static inline bool is_power_of_two(size_t in)
{
return (in & (in - 1)) == 0;
}
int main()
{
size_t block[4] = {32, 64, 256, 1024};
bool ret = pool_init(block, sizeof(block)/sizeof(block[0]));
void* data1 = pool_malloc(16);
void* data2 = pool_malloc(65);
void* data3 = pool_malloc(1024);
return 0;
}
```
my_pool_init, right before the indentedunalloc_space,line. That and the next two lines look like parameters to a function call but there is no function call and it won't currently compile. \$\endgroup\$is_power_of_twowill returntruewheninis 0. \$\endgroup\$pool_heapandg_poolsare missing. The header files included are also missing (stdint.h,stdbool.h, andstring.h), At the moment there is too much missing for us to review the code. \$\endgroup\$