libbpg/tmalloc.c
2015-01-16 13:46:18 +01:00

314 lines
8.4 KiB
C

/*
* Tiny malloc
*
* Copyright (c) 2014 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <limits.h>
#ifndef MALLOC_TEST
#define NDEBUG
#endif
#include <assert.h>
/*
* Note: only works for 32 bit pointers
*/
#define MALLOC_ALIGN 8
#define MALLOC_BLOCK_SIZE 32
#define STATE_FREE 0xaa
#define STATE_ALLOCATED 0x55
struct list_head {
struct list_head *prev, *next;
};
#define list_entry(el, type, member) \
((type *)((uint8_t *)(el) - offsetof(type, member)))
/* Note: the 'state' byte is stored just before the MemBlock header,
so at most 23 bytes can be allocated in a single block. */
typedef struct MemBlock {
struct list_head link;
union {
uint8_t data[0] __attribute((aligned(MALLOC_ALIGN)));
struct list_head free_link;
} u;
} MemBlock;
void *sbrk(intptr_t increment);
/* Invariants: the last block is always a free block. The last free
block is always the last block. */
static struct list_head free_list;
static struct list_head block_list;
static uint8_t *mem_top;
/* insert 'el' after prev */
static void list_add(struct list_head *el, struct list_head *prev)
{
struct list_head *next = prev->next;
prev->next = el;
el->prev = prev;
el->next = next;
next->prev = el;
}
static void list_del(struct list_head *el)
{
struct list_head *prev, *next;
prev = el->prev;
next = el->next;
prev->next = next;
next->prev = prev;
}
static size_t get_alloc_size(size_t size)
{
size = offsetof(MemBlock, u.data) + size;
/* one more byte for the state byte from the next block */
size = (size + MALLOC_BLOCK_SIZE) & ~(MALLOC_BLOCK_SIZE - 1);
return size;
}
/* Note: this size includes the 'state' byte from the next block */
static size_t get_block_size(MemBlock *p)
{
uint8_t *end;
struct list_head *el;
el = p->link.next;
if (el == &block_list)
end = mem_top;
else
end = (uint8_t *)list_entry(el, MemBlock, link);
return end - (uint8_t *)p;
}
static inline void set_block_state(MemBlock *p, int state)
{
((uint8_t *)p)[-1] = state;
}
static inline int get_block_state(const MemBlock *p)
{
return ((const uint8_t *)p)[-1];
}
void *malloc(size_t size)
{
MemBlock *p, *p1;
struct list_head *el;
size_t block_size;
if (size == 0 || size > (INT_MAX - 2 * MALLOC_BLOCK_SIZE))
return NULL;
if (free_list.next == NULL) {
/* init */
p = sbrk(MALLOC_BLOCK_SIZE * 2);
if (p == (void *)-1)
return NULL;
mem_top = sbrk(0);
free_list.prev = free_list.next = &free_list;
block_list.prev = block_list.next = &block_list;
p++;
set_block_state(p, STATE_FREE);
list_add(&p->link, &block_list);
list_add(&p->u.free_link, &free_list);
}
size = get_alloc_size(size);
el = free_list.next;
for(;;) {
p = list_entry(el, MemBlock, u.free_link);
assert(get_block_state(p) == STATE_FREE);
block_size = get_block_size(p);
if (size < block_size) {
goto done1;
} else if (el == free_list.prev) {
/* last free block: increase its size */
if (sbrk(size + MALLOC_BLOCK_SIZE - block_size) == (void *)-1)
return NULL;
mem_top = sbrk(0);
done1:
p1 = (MemBlock *)((uint8_t *)p + size);
list_add(&p1->link, &p->link);
list_add(&p1->u.free_link, &p->u.free_link);
set_block_state(p1, STATE_FREE);
list_del(&p->u.free_link);
done:
set_block_state(p, STATE_ALLOCATED);
return p->u.data;
} else if (size == block_size) {
list_del(&p->u.free_link);
goto done;
}
el = el->next;
}
}
void free(void *ptr)
{
MemBlock *p, *p1;
struct list_head *el;
if (!ptr)
return;
p = (MemBlock *)((uint8_t *)ptr - offsetof(MemBlock, u.data));
assert(get_block_state(p) == STATE_ALLOCATED);
/* mark as free */
list_add(&p->u.free_link, &free_list);
set_block_state(p, STATE_FREE);
/* merge with previous free block if possible */
el = p->link.prev;
if (el != &block_list) {
p1 = list_entry(el, MemBlock, link);
if (get_block_state(p1) == STATE_FREE) {
list_del(&p->link);
list_del(&p->u.free_link);
p = p1;
}
}
/* merge with next block if possible */
el = p->link.next;
if (el != &block_list) {
p1 = list_entry(el, MemBlock, link);
if (get_block_state(p1) == STATE_FREE) {
list_del(&p1->link);
/* keep p in the same position in free_list as p1 */
list_del(&p->u.free_link);
list_add(&p->u.free_link, &p1->u.free_link);
list_del(&p1->u.free_link);
}
}
}
void *realloc(void *ptr, size_t size)
{
MemBlock *p;
void *ptr1;
size_t size1;
if (ptr == NULL) {
return malloc(size);
} else if (size == 0) {
free(ptr);
return NULL;
} else {
p = (MemBlock *)((uint8_t *)ptr - offsetof(MemBlock, u.data));
assert(get_block_state(p) == STATE_ALLOCATED);
ptr1 = malloc(size);
if (!ptr1)
return NULL;
/* Note: never the last block so it is valid */
size1 = (uint8_t *)list_entry(p->link.next, MemBlock, link) -
p->u.data - 1;
if (size < size1)
size1 = size;
memcpy(ptr1, ptr, size1);
free(ptr);
return ptr1;
}
}
#ifdef MALLOC_TEST
static void malloc_check(void)
{
MemBlock *p;
struct list_head *el;
int state;
for(el = block_list.next; el != &block_list; el = el->next) {
p = list_entry(el, MemBlock, link);
state = get_block_state(p);
assert(state == STATE_FREE || state == STATE_ALLOCATED);
if (el->next != &block_list)
assert(el->next > el);
}
for(el = free_list.next; el != &free_list; el = el->next) {
p = list_entry(el, MemBlock, u.free_link);
assert(get_block_state(p) == STATE_FREE);
}
/* check invariant */
el = free_list.prev;
if (el != &free_list) {
p = list_entry(el, MemBlock, u.free_link);
assert(&p->link == block_list.prev);
}
}
static void malloc_dump(void)
{
MemBlock *p;
struct list_head *el;
printf("blocks:\n");
for(el = block_list.next; el != &block_list; el = el->next) {
p = list_entry(el, MemBlock, link);
printf("block: %p next=%p free=%d size=%u\n", p, p->link.next,
get_block_state(p) == STATE_FREE,
(unsigned int)get_block_size(p));
}
printf("free list:\n");
for(el = free_list.next; el != &free_list; el = el->next) {
p = list_entry(el, MemBlock, u.free_link);
printf("block: %p size=%u\n", p, (unsigned int)get_block_size(p));
}
}
int main(int argc, char **argv)
{
int i, n, j, size;
void **tab;
n = 100;
tab = malloc(sizeof(void *) * n);
memset(tab, 0, n * sizeof(void *));
for(i = 0; i < n * 1000; i++) {
j = random() % n;
free(tab[j]);
malloc_check();
size = random() % 500;
tab[j] = malloc(size);
memset(tab[j], 0x11, size);
malloc_check();
}
malloc_dump();
for(i = 0; i < n; i++) {
free(tab[i]);
}
return 0;
}
#endif