├── 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 |
--------------------------------------------------------------------------------