Chris@16
|
1 // Copyright (C) 2000, 2001 Stephen Cleary
|
Chris@16
|
2 //
|
Chris@16
|
3 // Distributed under the Boost Software License, Version 1.0. (See
|
Chris@16
|
4 // accompanying file LICENSE_1_0.txt or copy at
|
Chris@16
|
5 // http://www.boost.org/LICENSE_1_0.txt)
|
Chris@16
|
6 //
|
Chris@16
|
7 // See http://www.boost.org for updates, documentation, and revision history.
|
Chris@16
|
8
|
Chris@16
|
9 #ifndef BOOST_SIMPLE_SEGREGATED_STORAGE_HPP
|
Chris@16
|
10 #define BOOST_SIMPLE_SEGREGATED_STORAGE_HPP
|
Chris@16
|
11
|
Chris@16
|
12 /*!
|
Chris@16
|
13 \file
|
Chris@16
|
14 \brief Simple Segregated Storage.
|
Chris@16
|
15 \details A simple segregated storage implementation:
|
Chris@16
|
16 simple segregated storage is the basic idea behind the Boost Pool library.
|
Chris@16
|
17 Simple segregated storage is the simplest, and probably the fastest,
|
Chris@16
|
18 memory allocation/deallocation algorithm.
|
Chris@16
|
19 It begins by partitioning a memory block into fixed-size chunks.
|
Chris@16
|
20 Where the block comes from is not important until implementation time.
|
Chris@16
|
21 A Pool is some object that uses Simple Segregated Storage in this fashion.
|
Chris@16
|
22 */
|
Chris@16
|
23
|
Chris@16
|
24 // std::greater
|
Chris@16
|
25 #include <functional>
|
Chris@16
|
26
|
Chris@16
|
27 #include <boost/pool/poolfwd.hpp>
|
Chris@16
|
28
|
Chris@16
|
29 #ifdef BOOST_MSVC
|
Chris@16
|
30 #pragma warning(push)
|
Chris@16
|
31 #pragma warning(disable:4127) // Conditional expression is constant
|
Chris@16
|
32 #endif
|
Chris@16
|
33
|
Chris@16
|
34 #ifdef BOOST_POOL_VALIDATE
|
Chris@16
|
35 # define BOOST_POOL_VALIDATE_INTERNALS validate();
|
Chris@16
|
36 #else
|
Chris@16
|
37 # define BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
38 #endif
|
Chris@16
|
39
|
Chris@16
|
40 namespace boost {
|
Chris@16
|
41
|
Chris@16
|
42 /*!
|
Chris@16
|
43
|
Chris@16
|
44 \brief Simple Segregated Storage is the simplest, and probably the fastest,
|
Chris@16
|
45 memory allocation/deallocation algorithm. It is responsible for
|
Chris@16
|
46 partitioning a memory block into fixed-size chunks: where the block comes from
|
Chris@16
|
47 is determined by the client of the class.
|
Chris@16
|
48
|
Chris@16
|
49 \details Template class simple_segregated_storage controls access to a free list of memory chunks.
|
Chris@16
|
50 Please note that this is a very simple class, with preconditions on almost all its functions. It is intended to
|
Chris@16
|
51 be the fastest and smallest possible quick memory allocator - e.g., something to use in embedded systems.
|
Chris@16
|
52 This class delegates many difficult preconditions to the user (i.e., alignment issues).
|
Chris@16
|
53
|
Chris@16
|
54 An object of type simple_segregated_storage<SizeType> is empty if its free list is empty.
|
Chris@16
|
55 If it is not empty, then it is ordered if its free list is ordered. A free list is ordered if repeated calls
|
Chris@16
|
56 to <tt>malloc()</tt> will result in a constantly-increasing sequence of values, as determined by <tt>std::less<void *></tt>.
|
Chris@16
|
57 A member function is <i>order-preserving</i> if the free list maintains its order orientation (that is, an
|
Chris@16
|
58 ordered free list is still ordered after the member function call).
|
Chris@16
|
59
|
Chris@16
|
60 */
|
Chris@16
|
61 template <typename SizeType>
|
Chris@16
|
62 class simple_segregated_storage
|
Chris@16
|
63 {
|
Chris@16
|
64 public:
|
Chris@16
|
65 typedef SizeType size_type;
|
Chris@16
|
66
|
Chris@16
|
67 private:
|
Chris@16
|
68 simple_segregated_storage(const simple_segregated_storage &);
|
Chris@16
|
69 void operator=(const simple_segregated_storage &);
|
Chris@16
|
70
|
Chris@16
|
71 static void * try_malloc_n(void * & start, size_type n,
|
Chris@16
|
72 size_type partition_size);
|
Chris@16
|
73
|
Chris@16
|
74 protected:
|
Chris@16
|
75 void * first; /*!< This data member is the free list.
|
Chris@16
|
76 It points to the first chunk in the free list,
|
Chris@16
|
77 or is equal to 0 if the free list is empty.
|
Chris@16
|
78 */
|
Chris@16
|
79
|
Chris@16
|
80 void * find_prev(void * ptr);
|
Chris@16
|
81
|
Chris@16
|
82 // for the sake of code readability :)
|
Chris@16
|
83 static void * & nextof(void * const ptr)
|
Chris@16
|
84 { //! The return value is just *ptr cast to the appropriate type. ptr must not be 0. (For the sake of code readability :)
|
Chris@16
|
85 //! As an example, let us assume that we want to truncate the free list after the first chunk.
|
Chris@16
|
86 //! That is, we want to set *first to 0; this will result in a free list with only one entry.
|
Chris@16
|
87 //! The normal way to do this is to first cast first to a pointer to a pointer to void,
|
Chris@16
|
88 //! and then dereference and assign (*static_cast<void **>(first) = 0;).
|
Chris@16
|
89 //! This can be done more easily through the use of this convenience function (nextof(first) = 0;).
|
Chris@16
|
90 //! \returns dereferenced pointer.
|
Chris@16
|
91 return *(static_cast<void **>(ptr));
|
Chris@16
|
92 }
|
Chris@16
|
93
|
Chris@16
|
94 public:
|
Chris@16
|
95 // Post: empty()
|
Chris@16
|
96 simple_segregated_storage()
|
Chris@16
|
97 :first(0)
|
Chris@16
|
98 { //! Construct empty storage area.
|
Chris@16
|
99 //! \post empty()
|
Chris@16
|
100 }
|
Chris@16
|
101
|
Chris@16
|
102 static void * segregate(void * block,
|
Chris@16
|
103 size_type nsz, size_type npartition_sz,
|
Chris@16
|
104 void * end = 0);
|
Chris@16
|
105
|
Chris@16
|
106 // Same preconditions as 'segregate'
|
Chris@16
|
107 // Post: !empty()
|
Chris@16
|
108 void add_block(void * const block,
|
Chris@16
|
109 const size_type nsz, const size_type npartition_sz)
|
Chris@16
|
110 { //! Add block
|
Chris@16
|
111 //! Segregate this block and merge its free list into the
|
Chris@16
|
112 //! free list referred to by "first".
|
Chris@16
|
113 //! \pre Same as segregate.
|
Chris@16
|
114 //! \post !empty()
|
Chris@16
|
115 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
116 first = segregate(block, nsz, npartition_sz, first);
|
Chris@16
|
117 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
118 }
|
Chris@16
|
119
|
Chris@16
|
120 // Same preconditions as 'segregate'
|
Chris@16
|
121 // Post: !empty()
|
Chris@16
|
122 void add_ordered_block(void * const block,
|
Chris@16
|
123 const size_type nsz, const size_type npartition_sz)
|
Chris@16
|
124 { //! add block (ordered into list)
|
Chris@16
|
125 //! This (slower) version of add_block segregates the
|
Chris@16
|
126 //! block and merges its free list into our free list
|
Chris@16
|
127 //! in the proper order.
|
Chris@16
|
128 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
129 // Find where "block" would go in the free list
|
Chris@16
|
130 void * const loc = find_prev(block);
|
Chris@16
|
131
|
Chris@16
|
132 // Place either at beginning or in middle/end
|
Chris@16
|
133 if (loc == 0)
|
Chris@16
|
134 add_block(block, nsz, npartition_sz);
|
Chris@16
|
135 else
|
Chris@16
|
136 nextof(loc) = segregate(block, nsz, npartition_sz, nextof(loc));
|
Chris@16
|
137 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
138 }
|
Chris@16
|
139
|
Chris@16
|
140 // default destructor.
|
Chris@16
|
141
|
Chris@16
|
142 bool empty() const
|
Chris@16
|
143 { //! \returns true only if simple_segregated_storage is empty.
|
Chris@16
|
144 return (first == 0);
|
Chris@16
|
145 }
|
Chris@16
|
146
|
Chris@16
|
147 void * malloc BOOST_PREVENT_MACRO_SUBSTITUTION()
|
Chris@16
|
148 { //! Create a chunk.
|
Chris@16
|
149 //! \pre !empty()
|
Chris@16
|
150 //! Increment the "first" pointer to point to the next chunk.
|
Chris@16
|
151 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
152 void * const ret = first;
|
Chris@16
|
153
|
Chris@16
|
154 // Increment the "first" pointer to point to the next chunk.
|
Chris@16
|
155 first = nextof(first);
|
Chris@16
|
156 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
157 return ret;
|
Chris@16
|
158 }
|
Chris@16
|
159
|
Chris@16
|
160 void free BOOST_PREVENT_MACRO_SUBSTITUTION(void * const chunk)
|
Chris@16
|
161 { //! Free a chunk.
|
Chris@16
|
162 //! \pre chunk was previously returned from a malloc() referring to the same free list.
|
Chris@16
|
163 //! \post !empty()
|
Chris@16
|
164 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
165 nextof(chunk) = first;
|
Chris@16
|
166 first = chunk;
|
Chris@16
|
167 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
168 }
|
Chris@16
|
169
|
Chris@16
|
170 void ordered_free(void * const chunk)
|
Chris@16
|
171 { //! This (slower) implementation of 'free' places the memory
|
Chris@16
|
172 //! back in the list in its proper order.
|
Chris@16
|
173 //! \pre chunk was previously returned from a malloc() referring to the same free list
|
Chris@16
|
174 //! \post !empty().
|
Chris@16
|
175
|
Chris@16
|
176 // Find where "chunk" goes in the free list
|
Chris@16
|
177 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
178 void * const loc = find_prev(chunk);
|
Chris@16
|
179
|
Chris@16
|
180 // Place either at beginning or in middle/end.
|
Chris@16
|
181 if (loc == 0)
|
Chris@16
|
182 (free)(chunk);
|
Chris@16
|
183 else
|
Chris@16
|
184 {
|
Chris@16
|
185 nextof(chunk) = nextof(loc);
|
Chris@16
|
186 nextof(loc) = chunk;
|
Chris@16
|
187 }
|
Chris@16
|
188 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
189 }
|
Chris@16
|
190
|
Chris@16
|
191 void * malloc_n(size_type n, size_type partition_size);
|
Chris@16
|
192
|
Chris@16
|
193 //! \pre chunks was previously allocated from *this with the same
|
Chris@16
|
194 //! values for n and partition_size.
|
Chris@16
|
195 //! \post !empty()
|
Chris@16
|
196 //! \note If you're allocating/deallocating n a lot, you should
|
Chris@16
|
197 //! be using an ordered pool.
|
Chris@16
|
198 void free_n(void * const chunks, const size_type n,
|
Chris@16
|
199 const size_type partition_size)
|
Chris@16
|
200 {
|
Chris@16
|
201 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
202 if(n != 0)
|
Chris@16
|
203 add_block(chunks, n * partition_size, partition_size);
|
Chris@16
|
204 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
205 }
|
Chris@16
|
206
|
Chris@16
|
207 // pre: chunks was previously allocated from *this with the same
|
Chris@16
|
208 // values for n and partition_size.
|
Chris@16
|
209 // post: !empty()
|
Chris@16
|
210 void ordered_free_n(void * const chunks, const size_type n,
|
Chris@16
|
211 const size_type partition_size)
|
Chris@16
|
212 { //! Free n chunks from order list.
|
Chris@16
|
213 //! \pre chunks was previously allocated from *this with the same
|
Chris@16
|
214 //! values for n and partition_size.
|
Chris@16
|
215
|
Chris@16
|
216 //! \pre n should not be zero (n == 0 has no effect).
|
Chris@16
|
217 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
218 if(n != 0)
|
Chris@16
|
219 add_ordered_block(chunks, n * partition_size, partition_size);
|
Chris@16
|
220 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
221 }
|
Chris@16
|
222 #ifdef BOOST_POOL_VALIDATE
|
Chris@16
|
223 void validate()
|
Chris@16
|
224 {
|
Chris@16
|
225 int index = 0;
|
Chris@16
|
226 void* old = 0;
|
Chris@16
|
227 void* ptr = first;
|
Chris@16
|
228 while(ptr)
|
Chris@16
|
229 {
|
Chris@16
|
230 void* pt = nextof(ptr); // trigger possible segfault *before* we update variables
|
Chris@16
|
231 ++index;
|
Chris@16
|
232 old = ptr;
|
Chris@16
|
233 ptr = nextof(ptr);
|
Chris@16
|
234 }
|
Chris@16
|
235 }
|
Chris@16
|
236 #endif
|
Chris@16
|
237 };
|
Chris@16
|
238
|
Chris@16
|
239 //! Traverses the free list referred to by "first",
|
Chris@16
|
240 //! and returns the iterator previous to where
|
Chris@16
|
241 //! "ptr" would go if it was in the free list.
|
Chris@16
|
242 //! Returns 0 if "ptr" would go at the beginning
|
Chris@16
|
243 //! of the free list (i.e., before "first").
|
Chris@16
|
244
|
Chris@16
|
245 //! \note Note that this function finds the location previous to where ptr would go
|
Chris@16
|
246 //! if it was in the free list.
|
Chris@16
|
247 //! It does not find the entry in the free list before ptr
|
Chris@16
|
248 //! (unless ptr is already in the free list).
|
Chris@16
|
249 //! Specifically, find_prev(0) will return 0,
|
Chris@16
|
250 //! not the last entry in the free list.
|
Chris@16
|
251 //! \returns location previous to where ptr would go if it was in the free list.
|
Chris@16
|
252 template <typename SizeType>
|
Chris@16
|
253 void * simple_segregated_storage<SizeType>::find_prev(void * const ptr)
|
Chris@16
|
254 {
|
Chris@16
|
255 // Handle border case.
|
Chris@16
|
256 if (first == 0 || std::greater<void *>()(first, ptr))
|
Chris@16
|
257 return 0;
|
Chris@16
|
258
|
Chris@16
|
259 void * iter = first;
|
Chris@16
|
260 while (true)
|
Chris@16
|
261 {
|
Chris@16
|
262 // if we're about to hit the end, or if we've found where "ptr" goes.
|
Chris@16
|
263 if (nextof(iter) == 0 || std::greater<void *>()(nextof(iter), ptr))
|
Chris@16
|
264 return iter;
|
Chris@16
|
265
|
Chris@16
|
266 iter = nextof(iter);
|
Chris@16
|
267 }
|
Chris@16
|
268 }
|
Chris@16
|
269
|
Chris@16
|
270 //! Segregate block into chunks.
|
Chris@16
|
271 //! \pre npartition_sz >= sizeof(void *)
|
Chris@16
|
272 //! \pre npartition_sz = sizeof(void *) * i, for some integer i
|
Chris@16
|
273 //! \pre nsz >= npartition_sz
|
Chris@16
|
274 //! \pre Block is properly aligned for an array of object of
|
Chris@16
|
275 //! size npartition_sz and array of void *.
|
Chris@16
|
276 //! The requirements above guarantee that any pointer to a chunk
|
Chris@16
|
277 //! (which is a pointer to an element in an array of npartition_sz)
|
Chris@16
|
278 //! may be cast to void **.
|
Chris@16
|
279 template <typename SizeType>
|
Chris@16
|
280 void * simple_segregated_storage<SizeType>::segregate(
|
Chris@16
|
281 void * const block,
|
Chris@16
|
282 const size_type sz,
|
Chris@16
|
283 const size_type partition_sz,
|
Chris@16
|
284 void * const end)
|
Chris@16
|
285 {
|
Chris@16
|
286 // Get pointer to last valid chunk, preventing overflow on size calculations
|
Chris@16
|
287 // The division followed by the multiplication just makes sure that
|
Chris@16
|
288 // old == block + partition_sz * i, for some integer i, even if the
|
Chris@16
|
289 // block size (sz) is not a multiple of the partition size.
|
Chris@16
|
290 char * old = static_cast<char *>(block)
|
Chris@16
|
291 + ((sz - partition_sz) / partition_sz) * partition_sz;
|
Chris@16
|
292
|
Chris@16
|
293 // Set it to point to the end
|
Chris@16
|
294 nextof(old) = end;
|
Chris@16
|
295
|
Chris@16
|
296 // Handle border case where sz == partition_sz (i.e., we're handling an array
|
Chris@16
|
297 // of 1 element)
|
Chris@16
|
298 if (old == block)
|
Chris@16
|
299 return block;
|
Chris@16
|
300
|
Chris@16
|
301 // Iterate backwards, building a singly-linked list of pointers
|
Chris@16
|
302 for (char * iter = old - partition_sz; iter != block;
|
Chris@16
|
303 old = iter, iter -= partition_sz)
|
Chris@16
|
304 nextof(iter) = old;
|
Chris@16
|
305
|
Chris@16
|
306 // Point the first pointer, too
|
Chris@16
|
307 nextof(block) = old;
|
Chris@16
|
308
|
Chris@16
|
309 return block;
|
Chris@16
|
310 }
|
Chris@16
|
311
|
Chris@16
|
312 //! \pre (n > 0), (start != 0), (nextof(start) != 0)
|
Chris@16
|
313 //! \post (start != 0)
|
Chris@16
|
314 //! The function attempts to find n contiguous chunks
|
Chris@16
|
315 //! of size partition_size in the free list, starting at start.
|
Chris@16
|
316 //! If it succeds, it returns the last chunk in that contiguous
|
Chris@16
|
317 //! sequence, so that the sequence is known by [start, {retval}]
|
Chris@16
|
318 //! If it fails, it does do either because it's at the end of the
|
Chris@16
|
319 //! free list or hits a non-contiguous chunk. In either case,
|
Chris@16
|
320 //! it will return 0, and set start to the last considered
|
Chris@16
|
321 //! chunk. You are at the end of the free list if
|
Chris@16
|
322 //! nextof(start) == 0. Otherwise, start points to the last
|
Chris@16
|
323 //! chunk in the contiguous sequence, and nextof(start) points
|
Chris@16
|
324 //! to the first chunk in the next contiguous sequence (assuming
|
Chris@16
|
325 //! an ordered free list).
|
Chris@16
|
326 template <typename SizeType>
|
Chris@16
|
327 void * simple_segregated_storage<SizeType>::try_malloc_n(
|
Chris@16
|
328 void * & start, size_type n, const size_type partition_size)
|
Chris@16
|
329 {
|
Chris@16
|
330 void * iter = nextof(start);
|
Chris@16
|
331 while (--n != 0)
|
Chris@16
|
332 {
|
Chris@16
|
333 void * next = nextof(iter);
|
Chris@16
|
334 if (next != static_cast<char *>(iter) + partition_size)
|
Chris@16
|
335 {
|
Chris@16
|
336 // next == 0 (end-of-list) or non-contiguous chunk found
|
Chris@16
|
337 start = iter;
|
Chris@16
|
338 return 0;
|
Chris@16
|
339 }
|
Chris@16
|
340 iter = next;
|
Chris@16
|
341 }
|
Chris@16
|
342 return iter;
|
Chris@16
|
343 }
|
Chris@16
|
344
|
Chris@16
|
345 //! Attempts to find a contiguous sequence of n partition_sz-sized chunks. If found, removes them
|
Chris@16
|
346 //! all from the free list and returns a pointer to the first. If not found, returns 0. It is strongly
|
Chris@16
|
347 //! recommended (but not required) that the free list be ordered, as this algorithm will fail to find
|
Chris@16
|
348 //! a contiguous sequence unless it is contiguous in the free list as well. Order-preserving.
|
Chris@16
|
349 //! O(N) with respect to the size of the free list.
|
Chris@16
|
350 template <typename SizeType>
|
Chris@16
|
351 void * simple_segregated_storage<SizeType>::malloc_n(const size_type n,
|
Chris@16
|
352 const size_type partition_size)
|
Chris@16
|
353 {
|
Chris@16
|
354 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
355 if(n == 0)
|
Chris@16
|
356 return 0;
|
Chris@16
|
357 void * start = &first;
|
Chris@16
|
358 void * iter;
|
Chris@16
|
359 do
|
Chris@16
|
360 {
|
Chris@16
|
361 if (nextof(start) == 0)
|
Chris@16
|
362 return 0;
|
Chris@16
|
363 iter = try_malloc_n(start, n, partition_size);
|
Chris@16
|
364 } while (iter == 0);
|
Chris@16
|
365 void * const ret = nextof(start);
|
Chris@16
|
366 nextof(start) = nextof(iter);
|
Chris@16
|
367 BOOST_POOL_VALIDATE_INTERNALS
|
Chris@16
|
368 return ret;
|
Chris@16
|
369 }
|
Chris@16
|
370
|
Chris@16
|
371 } // namespace boost
|
Chris@16
|
372
|
Chris@16
|
373 #ifdef BOOST_MSVC
|
Chris@16
|
374 #pragma warning(pop)
|
Chris@16
|
375 #endif
|
Chris@16
|
376
|
Chris@16
|
377 #endif
|