|
1 | 1 | #include <stddef.h> |
2 | | -#include <stdlib.h> |
3 | 2 | #include <stdint.h> |
| 3 | +#include <stdlib.h> |
4 | 4 | #include <string.h> |
5 | 5 |
|
6 | | -typedef struct __attribute__((packed)) block |
7 | | -{ |
8 | | - struct block *ptr; |
9 | | - size_t size; |
| 6 | +typedef struct __attribute__((packed)) block { |
| 7 | + struct block *next; |
| 8 | + size_t total_size; |
| 9 | + uint8_t data[]; |
10 | 10 | } __attribute__((packed)) block_t; |
11 | 11 |
|
12 | 12 | extern uint8_t __heap_low[]; |
13 | 13 | extern uint8_t __heap_high[]; |
14 | 14 | static uintptr_t heap_ptr = (uintptr_t)__heap_low; |
15 | | -static block_t _alloc_base; |
| 15 | +// heap_base is the only node where total_size = 0 |
| 16 | +static block_t heap_base = { .next = NULL, .total_size = 0 }; |
16 | 17 |
|
| 18 | +#define BLOCK_HEADER_SIZE offsetof(block_t, data) |
| 19 | + |
| 20 | +// malloc(0) returns NULL |
17 | 21 | void *malloc(size_t alloc_size) |
18 | 22 | { |
19 | | - block_t *q; |
20 | | - block_t *r; |
21 | | - |
22 | | - /* add size of block header to real size */ |
23 | | - const size_t size = alloc_size + sizeof(block_t); |
24 | | - /* abort if alloc_size is 0 or size overflowed */ |
25 | | - if (size <= alloc_size) |
26 | | - { |
| 23 | + const size_t block_size = alloc_size + BLOCK_HEADER_SIZE; |
| 24 | + if (block_size <= BLOCK_HEADER_SIZE) { |
| 25 | + // reject if alloc_size is 0 or size overflowed |
27 | 26 | return NULL; |
28 | 27 | } |
29 | 28 |
|
30 | | - for (block_t *p = &_alloc_base; (q = p->ptr); p = q) |
31 | | - { |
32 | | - if (q->size >= size) |
33 | | - { |
34 | | - if (q->size <= size + sizeof(block_t)) |
35 | | - { |
36 | | - p->ptr = q->ptr; |
37 | | - } |
38 | | - else |
39 | | - { |
40 | | - q->size -= size; |
41 | | - q = (block_t*)(((uint8_t*)q) + q->size); |
42 | | - q->size = size; |
43 | | - } |
| 29 | + // search through the free-list for an open block (sorted by address) |
| 30 | + block_t *previous_block = &heap_base; |
| 31 | + while (previous_block->next != NULL) { |
| 32 | + block_t *current_block = previous_block->next; |
44 | 33 |
|
45 | | - return q + 1; |
| 34 | + if (current_block->total_size >= block_size) { |
| 35 | + if (current_block->total_size - BLOCK_HEADER_SIZE <= block_size) { |
| 36 | + // region is too small to split into two parts, so just claim the whole region |
| 37 | + previous_block->next = current_block->next; |
| 38 | + return current_block->data; |
| 39 | + } |
| 40 | + // split from the high end so the original free-list node stays valid |
| 41 | + current_block->total_size -= block_size; |
| 42 | + current_block = (block_t*)(((uint8_t*)current_block) + current_block->total_size); |
| 43 | + current_block->total_size = block_size; |
| 44 | + return current_block->data; |
46 | 45 | } |
| 46 | + |
| 47 | + previous_block = previous_block->next; |
47 | 48 | } |
48 | 49 |
|
49 | | - /* compute next heap pointer */ |
50 | | - if (heap_ptr + size < heap_ptr || heap_ptr + size >= (uintptr_t)__heap_high) |
51 | | - { |
| 50 | + // no suitable free block exists, extend into fresh heap space |
| 51 | + size_t heap_available = (uintptr_t)__heap_high - (uintptr_t)heap_ptr; |
| 52 | + if (block_size > heap_available) { |
52 | 53 | return NULL; |
53 | 54 | } |
54 | 55 |
|
55 | | - r = (block_t*)heap_ptr; |
56 | | - r->size = size; |
| 56 | + block_t *new_block = (block_t*)heap_ptr; |
| 57 | + new_block->total_size = block_size; |
| 58 | + heap_ptr = heap_ptr + block_size; |
57 | 59 |
|
58 | | - heap_ptr = heap_ptr + size; |
59 | | - |
60 | | - return r + 1; |
| 60 | + return new_block->data; |
61 | 61 | } |
62 | 62 |
|
63 | 63 | void free(void *ptr) |
64 | 64 | { |
65 | | - if (ptr != NULL) |
66 | | - { |
67 | | - block_t *p; |
68 | | - block_t *q; |
69 | | - |
70 | | - q = (block_t*)ptr - 1; |
| 65 | + if (ptr == NULL) { |
| 66 | + return; |
| 67 | + } |
71 | 68 |
|
72 | | - for (p = &_alloc_base; p->ptr && p->ptr < q; p = p->ptr); |
| 69 | + block_t *previous_block = &heap_base; |
| 70 | + block_t *current_block = (block_t*)((uint8_t*)ptr - BLOCK_HEADER_SIZE); |
| 71 | + while ( |
| 72 | + previous_block->next != NULL && |
| 73 | + (uintptr_t)previous_block->next < (uintptr_t)current_block |
| 74 | + ) { |
| 75 | + previous_block = previous_block->next; |
| 76 | + } |
73 | 77 |
|
74 | | - if (p->ptr && (uint8_t*)p->ptr == ((uint8_t*)q) + q->size) |
75 | | - { |
76 | | - q->size += p->ptr->size; |
77 | | - q->ptr = p->ptr->ptr; |
78 | | - } |
79 | | - else |
80 | | - { |
81 | | - q->ptr = p->ptr; |
82 | | - } |
| 78 | + // merge with the following free block if it is directly adjacent |
| 79 | + if ( |
| 80 | + previous_block->next != NULL && |
| 81 | + (uint8_t*)previous_block->next == ((uint8_t*)current_block) + current_block->total_size |
| 82 | + ) { |
| 83 | + current_block->total_size += previous_block->next->total_size; |
| 84 | + current_block->next = previous_block->next->next; |
| 85 | + } else { |
| 86 | + current_block->next = previous_block->next; |
| 87 | + } |
83 | 88 |
|
84 | | - if (p->size && ((uint8_t*)p) + p->size == (uint8_t*)q) |
85 | | - { |
86 | | - p->size += q->size; |
87 | | - p->ptr = q->ptr; |
88 | | - } |
89 | | - else |
90 | | - { |
91 | | - p->ptr = q; |
92 | | - } |
| 89 | + // merge with the preceding free block, unless that predecessor is heap_base |
| 90 | + if ( |
| 91 | + previous_block->total_size != 0 && |
| 92 | + ((uint8_t*)previous_block) + previous_block->total_size == (uint8_t*)current_block |
| 93 | + ) { |
| 94 | + previous_block->total_size += current_block->total_size; |
| 95 | + previous_block->next = current_block->next; |
| 96 | + } else { |
| 97 | + previous_block->next = current_block; |
93 | 98 | } |
94 | 99 | } |
95 | 100 |
|
96 | | -void *realloc(void *ptr, size_t size) |
| 101 | +// realloc(ptr, 0) returns ptr and does nothing |
| 102 | +void *realloc(void *ptr, size_t alloc_size) |
97 | 103 | { |
98 | | - block_t *h; |
99 | | - void *p; |
100 | | - |
101 | | - if (ptr == NULL) |
102 | | - { |
103 | | - return malloc(size); |
| 104 | + if (ptr == NULL) { |
| 105 | + return malloc(alloc_size); |
104 | 106 | } |
105 | 107 |
|
106 | | - h = (block_t*)ptr - 1; |
107 | | - |
108 | | - if (h->size >= size + sizeof(block_t)) |
109 | | - { |
| 108 | + block_t *header = (block_t*)((uint8_t*)ptr - BLOCK_HEADER_SIZE); |
| 109 | + if (header->total_size - BLOCK_HEADER_SIZE >= alloc_size) { |
| 110 | + // realloc(ptr, 0) is undefined in C23 and returns here |
110 | 111 | return ptr; |
111 | 112 | } |
112 | 113 |
|
113 | | - p = malloc(size); |
114 | | - if (p == NULL) |
115 | | - { |
| 114 | + // increase allocation size |
| 115 | + void *new_ptr = malloc(alloc_size); |
| 116 | + if (new_ptr == NULL) { |
116 | 117 | return NULL; |
117 | 118 | } |
118 | 119 |
|
119 | | - memcpy(p, ptr, h->size - sizeof(block_t)); |
| 120 | + memcpy(new_ptr, ptr, header->total_size - BLOCK_HEADER_SIZE); |
120 | 121 | free(ptr); |
121 | 122 |
|
122 | | - return p; |
| 123 | + return new_ptr; |
123 | 124 | } |
0 commit comments