├── LICENSE ├── README.md ├── allocator.c ├── allocator.h └── sample.c /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a modern refactoring of the simple "*storage allocator*" found in the classic K&R TCPL book. 2 | 3 | It retains the simplicity and efficiency of the original, adding a few improvements : 4 | - Localised, encapsulated state : 5 | 6 | The K&R allocator uses a global freelist, so there is **one** *global* allocator. 7 | Instead, we load/store the freelist and other state in `struct allocator`s, so there may be any number 8 | of independent, scope-bound allocator *objects*. 9 | - Custom memory pools : 10 | 11 | Where the original obtains memory from the OS through a syscall, 12 | here the *user* adds memory region(s) to an allocator object via `allocator_add()`. 13 | - Safe, correct & portable : 14 | 15 | This allocator is thread-safe using lightweight syncronisation techniques - spinlocks composed of C11 atomics. 16 | 17 | It also checks for integer overflow and treats this as an allocation error and handles alignment 18 | in a machine-independant way. 19 | In fact, the library is *virtually freestanding*. 20 | - Queryable : 21 | 22 | Like most modern allocators, this one too can query the *real size* of an allocation, with `allocator_allocsz()`. 23 | It also allows for querying each block's size in the freelist using `allocator_for_blocks()`. The K&R allocator is, in contrast, entirely opaque. 24 | -------------------------------------------------------------------------------- /allocator.c: -------------------------------------------------------------------------------- 1 | #include "allocator.h" 2 | #include /* bool, true, false */ 3 | #include /* uintptr_t, uint_fast8_t */ 4 | #include /* alignof */ 5 | #include /* memcpy() */ 6 | 7 | /* A FreeNode header prefixes blocks in the freelist, 8 | * putting them in a circular singly-linked list and 9 | * storing size information. 10 | */ 11 | typedef struct FreeNode { 12 | /* block size in terms of UNITSZ */ 13 | size_t nunits; 14 | struct FreeNode *nxt; 15 | /* aligning header to strictest type also aligns blocks */ 16 | allocator_align_t _align[]; 17 | } FreeNode; 18 | 19 | enum {UNITSZ = sizeof(FreeNode)}; 20 | 21 | /* Returns least value to add to base to align it to aln */ 22 | static inline uint_fast8_t aln_offset(uintptr_t base, uint_fast8_t aln) 23 | { 24 | return base%aln? aln - base%aln : 0; 25 | } 26 | 27 | #if ATOMIC_BOOL_LOCK_FREE == 2 28 | /* Test and test-and-set */ 29 | static inline void spinlock(atomic_bool *lock) 30 | { 31 | retry : 32 | if (atomic_exchange_explicit(lock, true, memory_order_acquire)) { 33 | /* As it loops on a load, this performs better 34 | * on many processors where atomic loads are cheaper 35 | * than atomic exchanges. 36 | * This is why atomic_bool is preferred for the lock if 37 | * it is lock-free, as atomic_flag cannot be loaded. 38 | */ 39 | while (atomic_load_explicit(lock, memory_order_acquire)) 40 | ; 41 | goto retry; 42 | } 43 | } 44 | static inline void spinunlock(atomic_bool *lock) 45 | { 46 | atomic_store_explicit(lock, false, memory_order_release); 47 | } 48 | #else 49 | /* Test-and-set */ 50 | static inline void spinlock(atomic_flag *lock) 51 | { 52 | while (atomic_flag_test_and_set_explicit(lock, memory_order_acquire)) 53 | ; 54 | } 55 | static inline void spinunlock(atomic_flag *lock) 56 | { 57 | atomic_flag_clear_explicit(lock, memory_order_release); 58 | } 59 | #endif 60 | 61 | /* K&R style next fit allocator */ 62 | void *allocator_alloc(allocator *a, size_t nbytes) 63 | { 64 | /* If a is NULL 65 | * or 0 size allocation is requested 66 | * or rounding up the requested size would overflow, 67 | * or allocator's freelist pointer is NULL (no freelist) 68 | * return NULL. 69 | */ 70 | 71 | uint_fast8_t inc = aln_offset(nbytes, UNITSZ); 72 | if (!a || !nbytes || SIZE_MAX-inc < nbytes) 73 | return NULL; 74 | 75 | void *res = NULL; 76 | spinlock(&a->lock); 77 | if (a->p) { 78 | /* Round up nbytes to number of units, +1 unit for header */ 79 | size_t nunits = (nbytes+inc)/UNITSZ + 1; 80 | 81 | for (FreeNode *prv = a->p, *cur = prv->nxt ;; prv = cur, cur = cur->nxt) { 82 | if (cur->nunits >= nunits) { /* match found */ 83 | if (cur->nunits == nunits) { /* unlink block */ 84 | if (prv->nxt != cur->nxt) 85 | prv->nxt = cur->nxt; 86 | else /* freelist is singleton */ 87 | prv = NULL; /* No freelist! */ 88 | } else /* adjust size & allocate from tail */ 89 | (cur += (cur->nunits -= nunits))->nunits = nunits; 90 | a->p = prv; /* Aids freelist consistency */ 91 | res = cur+1; /* Usable region after header */ 92 | break; 93 | } else if (cur == a->p) /* wrapped around, no match */ 94 | break; 95 | } 96 | } 97 | spinunlock(&a->lock); 98 | return res; 99 | } 100 | 101 | /* Return ptr to a's freelist */ 102 | void allocator_free(allocator *a, void *restrict ptr) 103 | { 104 | FreeNode *p = ptr; 105 | if (a && p--) { /* No-op if either a or p is NULL */ 106 | spinlock(&a->lock); 107 | if (a->p) { 108 | /* Freelist is in ascending order of addresses, 109 | * traverse to reach insertion point. 110 | */ 111 | FreeNode *cur; 112 | for (cur = a->p; !(p > cur && p < cur->nxt); cur = cur->nxt) { 113 | if(cur >= cur->nxt && (p > cur || p < cur->nxt)) 114 | break; 115 | } 116 | 117 | if (p + p->nunits == cur->nxt) { /* Coalesce to nxt */ 118 | p->nunits += cur->nxt->nunits; 119 | p->nxt = cur->nxt->nxt; 120 | } else /* Insert p after cur */ 121 | p->nxt = cur->nxt; 122 | if (cur + cur->nunits == p) { /* Coalesce to prv */ 123 | cur->nunits += p->nunits; 124 | cur->nxt = p->nxt; 125 | } else /* Insert after cur */ 126 | cur->nxt = p; 127 | a->p = cur; 128 | } else /* If no freelist, create singleton with p */ 129 | a->p = p, p->nxt = p; 130 | spinunlock(&a->lock); 131 | } 132 | } 133 | 134 | void allocator_add(allocator *a, void *restrict p, size_t nbytes) 135 | { 136 | uintptr_t addr = (uintptr_t)p; 137 | uint_fast8_t inc = aln_offset(addr, alignof(allocator_align_t)); 138 | size_t nunits = (nbytes - inc)/UNITSZ; /* Round down */ 139 | 140 | /* Ensure a is not NULL, aligned pointer doesn't exceed bounds, 141 | * and given size is of at least one unit. 142 | */ 143 | if (nbytes > inc+UNITSZ && nunits) { 144 | FreeNode *new = (FreeNode *)(addr+inc); /* Create header */ 145 | new->nunits = nunits; 146 | allocator_free(a, new+1); 147 | } 148 | } 149 | 150 | static inline size_t node_usable_space(const FreeNode *n) 151 | { 152 | return (n->nunits-1) * UNITSZ; 153 | } 154 | 155 | size_t allocator_allocsz(allocator *a, const void *restrict p) 156 | { 157 | size_t retval = 0; 158 | if (a && p) { 159 | spinlock(&a->lock); 160 | retval = node_usable_space((FreeNode *)p-1); 161 | spinunlock(&a->lock); 162 | } 163 | return retval; 164 | } 165 | 166 | void allocator_for_blocks(allocator *a, void(*f)(uintptr_t, size_t)) 167 | { 168 | if (a && f) { 169 | spinlock(&a->lock); 170 | if (a->p) { 171 | FreeNode *cur = a->p; 172 | do { 173 | f((uintptr_t)(cur+1), node_usable_space(cur)); 174 | cur = cur->nxt; 175 | } while (cur != a->p); 176 | } 177 | spinunlock(&a->lock); 178 | } 179 | } 180 | 181 | void *allocator_realloc(allocator *a, void *restrict p, size_t nbytes) 182 | { 183 | if (!p) 184 | return allocator_alloc(a, nbytes); 185 | else if (!nbytes) { 186 | allocator_free(a, p); 187 | return NULL; 188 | } else { 189 | void *res = p; 190 | size_t p_usable_space = allocator_allocsz(a, p); 191 | if ( 192 | p_usable_space < nbytes 193 | && (res = allocator_alloc(a, nbytes)) 194 | ) { 195 | memcpy(res, p, p_usable_space); 196 | allocator_free(a, p); 197 | } 198 | return res; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef ALLOCATOR_H 2 | #define ALLOCATOR_H 3 | 4 | #include /* size_t, max_align_t */ 5 | #include /* atomic_bool, ATOMIC_BOOL_LOCK_FREE, atomic_flag */ 6 | #include /* uintptr_t */ 7 | 8 | /* The type against which all allocations are aligned to by default. 9 | * MSVC does not define a max_align_t in its stddef.h, so we roll our own. 10 | * See also: 11 | * https://developercommunity.visualstudio.com/t/stdc11-should-add-max-align-t-to-stddefh/1386891 12 | * https://patchwork.ffmpeg.org/project/ffmpeg/patch/20240205195802.14522-1-anton@khirnov.net/ 13 | * https://github.com/llvm/llvm-project/blob/main/clang/lib/Headers/__stddef_max_align_t.h 14 | */ 15 | #ifdef _MSC_VER 16 | typedef union { 17 | long double a; 18 | long long b; 19 | void (*c)(void); 20 | } allocator_align_t; 21 | #else 22 | typedef max_align_t allocator_align_t; 23 | #endif 24 | 25 | /* Opaque type for nodes in the freelist */ 26 | typedef struct FreeNode FreeNode; 27 | 28 | /* Encapsulates allocator's state. 29 | * Treat all members as private. 30 | */ 31 | typedef struct allocator { 32 | FreeNode *p; 33 | #if ATOMIC_BOOL_LOCK_FREE == 2 34 | atomic_bool lock; 35 | #else 36 | atomic_flag lock; 37 | #endif 38 | } allocator; 39 | 40 | /* Constant expression to defualt-initialize an allocator */ 41 | #if ATOMIC_BOOL_LOCK_FREE == 2 42 | #define ALLOCATOR_INIT (allocator){0} 43 | #else 44 | #define ALLOCATOR_INIT (allocator){.lock = ATOMIC_FLAG_INIT} 45 | #endif 46 | 47 | /* Add memory region of n bytes refrenced by p to allocator's freelist. */ 48 | void allocator_add(allocator *, void *restrict p, size_t n); 49 | 50 | void *allocator_alloc(allocator *, size_t); 51 | void allocator_free(allocator *, void *restrict); 52 | void *allocator_realloc(allocator *, void *restrict, size_t); 53 | 54 | /* Obtain actual usable space, in bytes, in allocation refrenced by p */ 55 | size_t allocator_allocsz(allocator *a, const void *restrict p); 56 | 57 | /* Callsback with address & size (in bytes) of each block in freelist. 58 | * Don't call any allocator_* function inside callback, it will deadlock. 59 | */ 60 | void allocator_for_blocks(allocator *, void(*)(uintptr_t, size_t)); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /sample.c: -------------------------------------------------------------------------------- 1 | #include "allocator.h" 2 | #include 3 | #include /* strlen(), strcpy() */ 4 | #include /* PRIXPTR */ 5 | #include /* puts(), fputs(), stderr */ 6 | #include /* assert() */ 7 | 8 | /* Counter - track how many blocks we've been called with */ 9 | static size_t pblksz_cnt = 0; 10 | /* callback function for allocator_for_blocks() */ 11 | static void pblksz(uintptr_t blkaddr, size_t blksz) 12 | { 13 | /* A 0th node makes no sense, so pre-increment */ 14 | printf( 15 | "Block #%zu: 0x%"PRIXPTR", %zu bytes\n", 16 | ++pblksz_cnt, blkaddr, blksz 17 | ); 18 | } 19 | /* print msg and show the nodes in the freelist for a */ 20 | static void show_freelist(allocator *a, const char *restrict msg) 21 | { 22 | puts(msg); 23 | allocator_for_blocks(a, pblksz); 24 | pblksz_cnt = 0; /* reset count */ 25 | putchar('\n'); /* like a paragraph break */ 26 | } 27 | 28 | /* 4KiB heap composed of 4 equally-sized blocks */ 29 | #define HEAP_SIZE 4096 30 | #define NUMBER_OF_BLOCKS 4 31 | 32 | /* A type that is HEAP_SIZE/NUMBER_OF_BLOCKS bytes large and 33 | * aligned to the largest alignment supported. 34 | */ 35 | typedef allocator_align_t heap_block[ 36 | (HEAP_SIZE/NUMBER_OF_BLOCKS) / sizeof(allocator_align_t) 37 | ]; 38 | 39 | /* Extra block non-adjacent with the others (not in the array) */ 40 | static heap_block extra_block; 41 | 42 | int main(int argc, char **argv) 43 | { 44 | allocator a = ALLOCATOR_INIT; 45 | heap_block heap[NUMBER_OF_BLOCKS]; 46 | 47 | /* Test allocator_add() */ 48 | allocator_add(&a, &extra_block, sizeof(extra_block)); 49 | 50 | /* Test coalescing - blocks should be merged if they are adjacent */ 51 | for (size_t i = 0; i < NUMBER_OF_BLOCKS; i++) 52 | allocator_add(&a, heap+i, sizeof(heap[0])); 53 | 54 | show_freelist(&a, "Initial freelist :"); 55 | 56 | /* Test allocator_alloc() - deepcopy argv */ 57 | char **argv_copy = allocator_alloc(&a, argc*sizeof(char *)); 58 | assert(argv_copy); 59 | 60 | for (int i = 0; i < argc; i++) { 61 | argv_copy[i] = allocator_alloc(&a, strlen(argv[i])+1); 62 | assert(argv_copy[i]); 63 | strcpy(argv_copy[i], argv[i]); 64 | } 65 | 66 | show_freelist(&a, "Freelist after cloning argv :"); 67 | 68 | /* Test allocator_allocsz & allocator_free */ 69 | puts("Allocated:"); 70 | /* First thing we allocated was argv_copy itself! */ 71 | printf( 72 | "Block #0: @0x%"PRIXPTR", %zu bytes used of %zu\n", 73 | (uintmax_t)argv_copy, sizeof(argv_copy[0])*argc, 74 | allocator_allocsz(&a, argv_copy) 75 | ); 76 | /* Then we allocated & copied each arg inside it */ 77 | for (int i = 0; i < argc; i++) { 78 | char *cur = argv_copy[i]; 79 | printf( 80 | "Block #%d: \"%s\", %zu bytes used of %zu\n", 81 | i+1, cur, strlen(cur)+1, allocator_allocsz(&a, cur) 82 | ); 83 | allocator_free(&a, cur); 84 | } 85 | allocator_free(&a, argv_copy); 86 | putchar('\n'); 87 | 88 | show_freelist(&a, "Freelist after freeing :"); 89 | } 90 | --------------------------------------------------------------------------------