一般的にオブジェクト指向プログラミングにおいて、機能やデータはクラスで分けた方が後々、変更が楽りますし再利用性もが上がります。
複数のアロケーターインスタンスを作ったほうが、長期的にはメンテがしやすいってことです。
では前の例を少し変更して考えて見ましょう。
まずスタックアロケーターを作るという意味ですと、メモリープールは 1 次元で十分となります。
それとストレージ用の配列も 1 個あれば十分です。
すると以下のような allocator クラスに変更することができます。
12 template<std::size_t N>
13 class allocator
14 {
15 public:
16 typedef std::size_t size_type;
17
18 explicit inline allocator()
19 :
20 M_block_size_(sizeof(std::byte[N])),
21 M_max_objects_(MAX_OBJECTS),
22 M_head_(nullptr),
23 M_pool_index_(0)
24 {
25 Mp_pool_ = new (&Mp_Mem_) std::byte[sizeof(std::byte[N])*MAX_OBJECTS];
26 }
// 中略
57
58 private:
// 中略
82 std::byte* Mp_pool_;
83 unsigned M_pool_index_; //新規割当時にインクリメント、下がることはない
84 std::byte Mp_Mem_[sizeof(std::byte[N])*MAX_OBJECTS];
85 };Mp_pool_ は配列ではないですし Mp_mem_ も一つ分しかないですね。
後はテンプレート引数 N が Mp_mem_ の記憶域のサイズを定義しています。
次にマネージャークラス block_allocator です。
89 template<typename T>
90 class block_allocator
91 {
// 中略
169 private:
170 static allocator(1) S_alloc8_;
171 static allocator(2) S_alloc16_;
172 static allocator(3) S_alloc32_;
173 static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5) block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;このようにマネージャークラスにはアロケータークラス 4 個分のスタティックインスタンスを宣言・定義しておきます。
S_alloc8_ が 8 バイト均等ブロックの割り当て、S_alloc16_ が 16 バイト均等ブロックの割り当て、 S_alloc32_ が 32 バイト均等のブロックの割り当てを行います。
注意したいのは block_allocator<T> で(テンプレート引数の違う)インスタンスが 1 個増えるたびに 4 個分のアロケーターインスタンスが作られることですかね。
まあアロケーターのインスタンスはサイズからすりゃ大したことないんですけど。
ではフルソースで実装と検証をしてみましょう。
main.cpp.
1 #include <cstddef>
2 #include <memory>
3 #include <cassert>
4 #include <cstdlib>
5 #include <iostream>
6 #include <list>
7 #include <vector>
8
9 #define MAX_OBJECTS 32
10 #define MAX_ALLOCS 4
11
12 template<std::size_t N>
13 class allocator
14 {
15 public:
16 typedef std::size_t size_type;
17
18 explicit inline allocator()
19 :
20 M_block_size_(sizeof(std::byte[N])),
21 M_max_objects_(MAX_OBJECTS),
22 M_head_(nullptr),
23 M_pool_index_(0)
24 {
25 Mp_pool_ = new (&Mp_Mem_) std::byte[sizeof(std::byte[N])*MAX_OBJECTS];
26 }
27
28 inline ~allocator(){
29 }
30
31 inline void* allocate(){
32 void* pblock = pop_from_pool();
33 if(!pblock)
34 {
35 if(M_pool_index_ < M_max_objects_)
36 {
37 ++M_pool_index_;
38 pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
39 std::cout << M_block_size_ << ": M_pool_index=" << M_pool_index_ << " pblock=" << pblock << '\n';
40 } else {
41 std::cout << "M_pool_index = " << M_pool_index_ << '\n';
42 assert(false);
43 }
44 }
45 return pblock;
46 }
47
48 inline void deallocate(void* memory)
49 {
50 std::cout << "rewind allocated address: " << memory << '\n';
51 rewind(memory);
52 }
53
54 inline size_t get_block_size() const {
55 return M_block_size_;
56 }
57
58 private:
59 // 開放(deallocate)時に使用可能領域を更新
60 inline void rewind(void* memory){
61 block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
62 pblock->next = M_head_; //先頭ポインタを新規ポインタの next ポインタに設定
63 M_head_ = pblock; //新たなブロックを先頭ポインタに設定
64 }
65 // 割当(allocate)時に開放された使用可能領域から使う
66 inline void* pop_from_pool(){
67 block_not_in_use* pblock = nullptr;
68 if(M_head_)
69 {
70 pblock = M_head_;
71 M_head_ = M_head_->next;
72 }
73 return static_cast<void*>(pblock);
74 }
75 // 使用可能なメモリータグ(一度割当、後に開放された領域)
76 struct block_not_in_use {
77 block_not_in_use *next;
78 };
79 const size_t M_block_size_;
80 const unsigned M_max_objects_;
81 block_not_in_use* M_head_;
82 std::byte* Mp_pool_;
83 unsigned M_pool_index_; //新規割当時にインクリメント、下がることはない
84 std::byte Mp_Mem_[sizeof(std::byte[N])*MAX_OBJECTS];
85 };
86
87 /* スタックメモリーのブロックを割り当てるクラステンプレート */
88
89 template<typename T>
90 class block_allocator
91 {
92 public:
93 typedef T value_type;
94 typedef value_type* pointer;
95 typedef const value_type* const_pointer;
96 typedef value_type& reference;
97 typedef const value_type& const_reference;
98 typedef std::size_t size_type;
99 typedef std::ptrdiff_t difference_type;
100
101 template<typename U>
102 struct rebind
103 {
104 typedef block_allocator<U> other;
105 };
106
107 inline block_allocator() noexcept
108 {}
109 inline ~block_allocator() {}
110
111 inline explicit block_allocator(const block_allocator&) noexcept {}
112
113 template<typename U>
114 inline constexpr block_allocator(const block_allocator<U>&) noexcept
115 {}
116
117 inline pointer allocate(size_type n, typename std::allocator<T>::const_pointer = 0)
118 {
119 auto s = sizeof(T) * n;
120 if(s <= 8)
121 return reinterpret_cast<T*>(S_alloc8_.allocate());
122 else if(s <= 16)
123 return reinterpret_cast<T*>(S_alloc16_.allocate());
124 else if(s <= 32)
125 return reinterpret_cast<T*>(S_alloc32_.allocate());
126 else if(s <= 64)
127 return reinterpret_cast<T*>(S_alloc64_.allocate());
128 else
129 assert(false);
130 }
131
132 inline void deallocate(T* p, size_type n) noexcept
133 {
134 auto s = sizeof(T) * n;
135 if(s <= 8)
136 S_alloc8_.deallocate(reinterpret_cast<void*>(p));
137 else if(s <= 16)
138 S_alloc16_.deallocate(reinterpret_cast<void*>(p));
139 else if(s <= 32)
140 S_alloc32_.deallocate(reinterpret_cast<void*>(p));
141 else if(s <= 64)
142 S_alloc64_.deallocate(reinterpret_cast<void*>(p));
143 else
144 assert(false);
145 }
146
147 inline size_type max_size() const
148 {
149 return std::numeric_limits<size_type>::max();
150 }
151
152 static size_type capacity()
153 {
154 return MAX_OBJECTS;
155 }
156
157 template<typename U, typename ... Args>
158 void construct(U* p, Args&&... args) {
159 ::new ((void*)p) T(args...);
160 }
161
162 template<typename U>
163 void destroy(U* p)
164 {
165 std::cout << "destroy: " << p << '\n';
166 p->~U();
167 }
168
169 private:
170 static allocator(1) S_alloc8_;
171 static allocator(2) S_alloc16_;
172 static allocator(3) S_alloc32_;
173 static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5) block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;
187
188 /* voidパラメーターのエラーチェックのためにvoid型のクラステンプレートを用意 */
189
190 template <>
191 class block_allocator<void>
192 {
193 public:
194 typedef void* pointer;
195 typedef const void* const_pointer;
196 typedef void value_type;
197
198 template<typename U>
199 struct rebind
200 {
201 typedef block_allocator<U> other;
202 };
203
204 explicit block_allocator() noexcept {
205 }
206
207 template<typename U>
208 block_allocator(const block_allocator<U>&){}
209
210 template<typename U>
211 block_allocator(std::allocator<U>&){}
212
213 };
214
215 template<typename T>
216 using rebind_t = typename block_allocator<T>::template rebind<T>::type;
217
218 template<typename T, typename T2>
219 inline bool operator==(block_allocator<T> const&, block_allocator<T2> const&)
220 {
221 return true;
222 }
223
224 template<typename T, typename OtherAllocator>
225 inline bool operator==(block_allocator<T> const&, OtherAllocator const&)
226 {
227 return false;
228 }
229
230 /* カスタムアロケータ― */
231
232 template<typename Allocator, typename U>
233 struct block_rebind
234 {
235 typedef typename Allocator::template rebind<U>::other type;
236 };
237
238 /* カスタムアロケータ―型のエイリアス */
239
240 template<typename Allocator, typename U>
241 using block_rebind_t = typename block_rebind<Allocator,U>::type;
242
243 /* std::unique_ptr の引数に使う deleter */
244
245 template<typename Allocator, typename T>
246 class block_delete
247 {
248 public:
249 explicit block_delete(const Allocator& alloc) noexcept
250 : M_alloc_(alloc)
251 {
252 }
253 template<typename Other,typename U>
254 block_delete(const block_delete<Other,U>& d) noexcept
255 : M_alloc_(d.M_alloc_)
256 {}
257 void operator()(T* p) const
258 {
259 p->~T();
260 M_alloc_.deallocate(p,1);
261 }
262 private:
263 mutable block_rebind_t<Allocator,T> M_alloc_;
264 };
265
266 /* カスタムアロケータ―で割り当てるスマートポインターの型 */
267
268 template<typename Allocator, typename T>
269 using block_ptr = std::unique_ptr<T,block_delete<Allocator,T>>;
270
271 /* 割当関数 */
272
273 template<typename T, typename Allocator, typename... Args>
274 block_ptr<Allocator,T> allocate_block(const Allocator& alloc, Args&&... args)
275 {
276 block_rebind_t<Allocator,T> rebound_alloc(alloc);
277 T* praw = rebound_alloc.allocate(1);
278 try {
279 T* p = new (praw) T(std::forward<Args>(args)...);
280 return block_ptr<Allocator,T>(p,block_delete<Allocator,T>(rebound_alloc));
281 } catch (...) {
282 rebound_alloc.deallocate(praw,1);
283 }
284 }
285
286 struct A{
287 A(int x, int y, int z, int a) :
288 x_(x),
289 y_(y),
290 z_(z),
291 a_(a)
292 {}
293 int x_,y_,z_,a_;
294
295 };
296
297 int main()
298 {
299 using int_allocator = block_allocator<int>;
300 using a_allocator = block_allocator<A>;
301
302 int_allocator alloc;
303 int_allocator alloc_copy;
304 a_allocator alloc_a;
305
306 for(int i = 0; i < 2; ++i) {
307 auto l = allocate_block<int,int_allocator>(alloc,200);
308 // alloc_copy は alloc と同じ allocator を共有しています
309 auto a = allocate_block<int,int_allocator>(alloc_copy,600);
310 {
311 auto f = allocate_block<A,a_allocator>(alloc_a,1,2,3,4);
312 auto g = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
313 }
314 auto v = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
315 auto w = allocate_block<A,a_allocator>(alloc_a,15,16,17,18);
316 }
317
318 return 0;
319 }
ビルドと実行結果.
$ g++ selector.cpp -std=c++17 $ ./a.out 8: M_pool_index=1 pblock=0x555a4579816c 8: M_pool_index=2 pblock=0x555a45798174 16: M_pool_index=1 pblock=0x555a457992b4 16: M_pool_index=2 pblock=0x555a457992c4 rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a45798174 rewind allocated address: 0x555a4579816c rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a45798174 rewind allocated address: 0x555a4579816c
このセレクターはフリーリストを回避するための実装方法としての意味もあります。
まずは block_allocator クラステンプレートを見てみましょう。
87 /* スタックメモリーのブロックを割り当てるクラステンプレート */
88
89 template<typename T>
90 class block_allocator
91 {
92 public:
93 typedef T value_type;
94 typedef value_type* pointer;
95 typedef const value_type* const_pointer;
96 typedef value_type& reference;
97 typedef const value_type& const_reference;
98 typedef std::size_t size_type;
99 typedef std::ptrdiff_t difference_type;
100
101 template<typename U>
102 struct rebind
103 {
104 typedef block_allocator<U> other;
105 };
106
107 inline block_allocator() noexcept
108 {}
109 inline ~block_allocator() {}
110
111 inline explicit block_allocator(const block_allocator&) noexcept {}
112
113 template<typename U>
114 inline constexpr block_allocator(const block_allocator<U>&) noexcept
115 {}
116
117 inline pointer allocate(size_type n, typename std::allocator<T>::const_pointer = 0)
118 {
119 auto s = sizeof(T) * n;
120 if(s <= 8)
121 return reinterpret_cast<T*>(S_alloc8_.allocate());
122 else if(s <= 16)
123 return reinterpret_cast<T*>(S_alloc16_.allocate());
124 else if(s <= 32)
125 return reinterpret_cast<T*>(S_alloc32_.allocate());
126 else if(s <= 64)
127 return reinterpret_cast<T*>(S_alloc64_.allocate());
128 else
129 assert(false);
130 }
131
132 inline void deallocate(T* p, size_type n) noexcept
133 {
134 auto s = sizeof(T) * n;
135 if(s <= 8)
136 S_alloc8_.deallocate(reinterpret_cast<void*>(p));
137 else if(s <= 16)
138 S_alloc16_.deallocate(reinterpret_cast<void*>(p));
139 else if(s <= 32)
140 S_alloc32_.deallocate(reinterpret_cast<void*>(p));
141 else if(s <= 64)
142 S_alloc64_.deallocate(reinterpret_cast<void*>(p));
143 else
144 assert(false);
145 }
146
147 inline size_type max_size() const
148 {
149 return std::numeric_limits<size_type>::max();
150 }
151
152 static size_type capacity()
153 {
154 return MAX_OBJECTS;
155 }
156
157 template<typename U, typename ... Args>
158 void construct(U* p, Args&&... args) {
159 ::new ((void*)p) T(args...);
160 }
161
162 template<typename U>
163 void destroy(U* p)
164 {
165 std::cout << "destroy: " << p << '\n';
166 p->~U();
167 }
168
169 private:
170 static allocator(1) S_alloc8_;
171 static allocator(2) S_alloc16_;
172 static allocator(3) S_alloc32_;
173 static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5) block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;まあこれだけで巨大なんですが重要な箇所は allocate() 関数だけです。
117 inline pointer allocate(size_type n, typename std::allocator<T>::const_pointer = 0)
118 {
119 auto s = sizeof(T) * n;
120 if(s <= 8)
121 return reinterpret_cast<T*>(S_alloc8_.allocate());
122 else if(s <= 16)
123 return reinterpret_cast<T*>(S_alloc16_.allocate());
124 else if(s <= 32)
125 return reinterpret_cast<T*>(S_alloc32_.allocate());
126 else if(s <= 64)
127 return reinterpret_cast<T*>(S_alloc64_.allocate());
128 else
129 assert(false);
130 }sizeof(T) * n のサイズを一つのブロックサイズとして、各ブロックサイズのアロケーターへのセレクター機能を allocate() 関数が担当しています。
例えば 8 バイトブロックの 3 個分がリクエストされたら、リクエストサイズは 16 バイトと 32 バイトの間なので S_alloc32_ の allocate() をコールします。
16 バイトブロック 2 個分のリクエストサイズは 32 バイトになるので、 S_alloc32_ の allocate() をコールします。
16 バイトブロック 3 個分のリクエストサイズは 48 バイトになるので、 S_alloc64_ の allocate() が呼ばれます。
まあこんな感じで block_allocator の allocate() 関数が固定サイズアロケーターをセレクトしていきます。
この実装の利点は allocator クラステンプレート側に型名パラメーター T が存在しないことです。
9 #define MAX_OBJECTS 32
10 #define MAX_ALLOCS 4
11
12 template<std::size_t N>
13 class allocator
14 {
15 public:
16 typedef std::size_t size_type;
17
18 explicit inline allocator()
19 :
20 M_block_size_(sizeof(std::byte[N])),
21 M_max_objects_(MAX_OBJECTS),
22 M_head_(nullptr),
23 M_pool_index_(0)
24 {
25 Mp_pool_ = new (&Mp_Mem_) std::byte[sizeof(std::byte[N])*MAX_OBJECTS];
26 }つまり allocator はブロックサイズとリクエストサイズが等しいという前提で設計ができるので、リクエストサイズとフリーチャンクの情報のメンテナンスは不要になります。
28 inline ~allocator(){
29 }
30
31 inline void* allocate(){
32 void* pblock = pop_from_pool();
33 if(!pblock)
34 {
35 if(M_pool_index_ < M_max_objects_)
36 {
37 ++M_pool_index_;
38 pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
39 std::cout << M_block_size_ << ": M_pool_index=" << M_pool_index_ << " pblock=" << pblock << '\n';
40 } else {
41 std::cout << "M_pool_index = " << M_pool_index_ << '\n';
42 assert(false);
43 }
44 }
45 return pblock;
46 }
47
48 inline void deallocate(void* memory)
49 {
50 std::cout << "rewind allocated address: " << memory << '\n';
51 rewind(memory);
52 }このようにスタックアロケーターのロジックをかなり省略できたことにお気づきになれますかね?
もちろん呼び出し関数もよりシンプルになってます。
59 // 開放(deallocate)時に使用可能領域を更新
60 inline void rewind(void* memory){
61 block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
62 pblock->next = M_head_; //先頭ポインタを新規ポインタの next ポインタに設定
63 M_head_ = pblock; //新たなブロックを先頭ポインタに設定
64 }
65 // 割当(allocate)時に開放された使用可能領域から使う
66 inline void* pop_from_pool(){
67 block_not_in_use* pblock = nullptr;
68 if(M_head_)
69 {
70 pblock = M_head_;
71 M_head_ = M_head_->next;
72 }
73 return static_cast<void*>(pblock);
74 }
75 // 使用可能なメモリータグ(一度割当、後に開放された領域)
76 struct block_not_in_use {
77 block_not_in_use *next;
78 };rewind() は単にフリー(チャンク)リストの先頭を解放したフリーチャンクに設定し、前の先頭は next ポインターに設定されます。
pop_from_pool() も先頭からフリーチャンクを取り出して返すだけですね。
一旦説明を block_allocator クラステンプレートに戻したいと思います。
170 static allocator(1) S_alloc8_; 171 static allocator(2) S_alloc16_; 172 static allocator(3) S_alloc32_; 173 static allocator(4) S_alloc64_; 174 }; 175 176 template<typename T> 177 allocator(5) block_allocator<T>::S_alloc8_; 178 179 template<typename T> 180 allocator(6) block_allocator<T>::S_alloc16_; 181 182 template<typename T> 183 allocator(7) block_allocator<T>::S_alloc32_; 184 185 template<typename T> 186 allocator(8) block_allocator<T>::S_alloc64_;
このケースだとアロケーターは 4 個しかないですが、スタティックインスタンスをいくつも用意しておくのであれば 20 - 30 個分ぐらいあっても大丈夫です。
これスタックだから心配(ガクブル)とお考えになるかもしれないですが、大丈夫ですよ。
スタティックインスタンスが割り当てられるのはスタック領域じゃなくヒープ領域に割り当てられる(実装依存)という前提で考えれば良いと思います。
そもそもメモリーが足りないというよりフラグメンテーションが怖いケースの方が多いので、多少メモリー容量を無駄にしてでも実装する価値はあります。
テストコードについては、スマートポインター化するための allocate_block 関数テンプレートを使います。
230 /* カスタムアロケータ― */
231
232 template<typename Allocator, typename U>
233 struct block_rebind
234 {
235 typedef typename Allocator::template rebind<U>::other type;
236 };
237
238 /* カスタムアロケータ―型のエイリアス */
239
240 template<typename Allocator, typename U>
241 using block_rebind_t = typename block_rebind<Allocator,U>::type;
242
243 /* std::unique_ptr の引数に使う deleter */
244
245 template<typename Allocator, typename T>
246 class block_delete
247 {
248 public:
249 explicit block_delete(const Allocator& alloc) noexcept
250 : M_alloc_(alloc)
251 {
252 }
253 template<typename Other,typename U>
254 block_delete(const block_delete<Other,U>& d) noexcept
255 : M_alloc_(d.M_alloc_)
256 {}
257 void operator()(T* p) const
258 {
259 p->~T();
260 M_alloc_.deallocate(p,1);
261 }
262 private:
263 mutable block_rebind_t<Allocator,T> M_alloc_;
264 };
265
266 /* カスタムアロケータ―で割り当てるスマートポインターの型 */
267
268 template<typename Allocator, typename T>
269 using block_ptr = std::unique_ptr<T,block_delete<Allocator,T>>;
270
271 /* 割当関数 */
272
273 template<typename T, typename Allocator, typename... Args>
274 block_ptr<Allocator,T> allocate_block(const Allocator& alloc, Args&&... args)
275 {
276 block_rebind_t<Allocator,T> rebound_alloc(alloc);
277 T* praw = rebound_alloc.allocate(1);
278 try {
279 T* p = new (praw) T(std::forward<Args>(args)...);
280 return block_ptr<Allocator,T>(p,block_delete<Allocator,T>(rebound_alloc));
281 } catch (...) {
282 rebound_alloc.deallocate(praw,1);
283 }
284 }カスタムデリーターを実装して std::unique_ptr のインスタンスを作っているだけです。
ただリクエスト個数が 1 個に限定されているので、ここは引数を増やして対応するのもありっちゃありそうですが T 型のオブジェクトを複数個欲しいというのは STL コンテナが行う最適化なので STL コンテナを使わないのであれば、これで十分だと思います。
ではテストコードを見てみましょう。
286 struct A{
287 A(int x, int y, int z, int a) :
288 x_(x),
289 y_(y),
290 z_(z),
291 a_(a)
292 {}
293 int x_,y_,z_,a_;
294
295 };
296
297 int main()
298 {
299 using int_allocator = block_allocator<int>;
300 using a_allocator = block_allocator<A>;
301
302 int_allocator alloc;
303 int_allocator alloc_copy;
304 a_allocator alloc_a;
305
306 for(int i = 0; i < 2; ++i) {
307 auto l = allocate_block<int,int_allocator>(alloc,200);
308 // alloc_copy は alloc と同じ allocator を共有しています
309 auto a = allocate_block<int,int_allocator>(alloc_copy,600);
310 {
311 auto f = allocate_block<A,a_allocator>(alloc_a,1,2,3,4);
312 auto g = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
313 }
314 auto v = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
315 auto w = allocate_block<A,a_allocator>(alloc_a,15,16,17,18);
316 }
317
318 return 0;
319 }2 回 int 型を生成し、4 回 A 型のオブジェクトを生成していますね。
さらに A 型オブジェクトについては 2 回はブロックの内部にあるので有効期限が瞬時に切れて解放されます。
ついでに int 型オブジェクトもループが終了するたびに解放されます。
そのため以下のように新規割り当ては 2 回だけで、その後は既に割り当てたアドレスを再利用していきます。
8: M_pool_index=1 pblock=0x555a4579816c 8: M_pool_index=2 pblock=0x555a45798174 16: M_pool_index=1 pblock=0x555a457992b4 16: M_pool_index=2 pblock=0x555a457992c4 rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a45798174 rewind allocated address: 0x555a4579816c rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a457992c4 rewind allocated address: 0x555a457992b4 rewind allocated address: 0x555a45798174 rewind allocated address: 0x555a4579816c
これでセレクターについての説明は終わりですが、実装面に慣れてきたら、最終的にはメモリーをどう使いたいのかという設計の問題にいきつくでしょうね。
Copyright 2018-2019, by Masaki Komatsu