88.1. スタックアロケーター(可変長のリクエストサイズに対応、難易度高いのでスキップ推奨)

この項目では固定長のスタックアロケーターを紹介します。

スタックアロケーターは割り当てたいメモリーのサイズが可変長の場合だと、いくつか念頭にしておくべき点が出てきます。

実装の観点から感がえると、割り当てるサイズが固定長のサイズであり、解放は最後に割り当てたメモリー領域からする前提が一番シンプルです。

この条件が満たされるなら、以下のような関数の実装で割り当て、解放ができます。

allocate(リクエスト個数){
  スタックポインター += (リクエスト個数 * ブロックサイズ);
  return スタックポインター;
}

deallocate (リクエスト個数) {
  スタックポインター -= (リクエスト個数 * ブロックサイズ);
}

スタックポインターは割り当てるポインターとなり、リクエスト個数はアロケーター要件に準拠した allocate() や deallocate() の引数の一つです。

ブロックサイズは 1 回のメモリー割り当てに使える最低サイズ、つまり割り当ての最少単位です。

リクエスト個数とかブロックサイズが理解しにくければ「(割り当ての)リクエストサイズ = リクエスト個数 * ブロックサイズ」と考えると分かりやすいかもしれないですね。

allocate() と deallocate() は割り当てをすればスタックポインターの位置は上がり、解放すれば位置は下がるという具合になります。

ですがこのようなアロケーター実装は現実には全く使えません。

なぜならアロケーターは以下の 2 点を満たさないのなら、スタックのエミュレーターと何ら変わりがないからです。

この条件を満たすには、以下の 2 つを作り込む必要があります。

どちらも難しいですが実装してみましょう。

main.cpp. 

  1 #include <cstddef>
  2 #include <memory>
  3 #include <cassert>
  4 #include <new>
  5 #include <cstdlib>
  6 #include <iostream>
  7 #include <cstring>
  8 #include <map>
  9 #include <vector>
 10
 11 #define MAX_OBJECTS 32
 12
 13 template<typename T,size_t N>
 14 class allocator
 15 {
 16   struct block_not_in_use;
 17   typedef std::map<void*,size_t> size_map_t;
 18   typedef std::map<size_t,block_not_in_use*> free_list_t;
 19   static_assert(sizeof(std::byte[N]) >= sizeof(T));
 20 public:
 21
 22   allocator()
 23     :
 24     M_max_objects_(MAX_OBJECTS),
 25     M_pool_index_(0),
 26     M_block_size_(sizeof(std::byte[N]) < 8 ? sizeof(long long) : sizeof(std::byte[N]))
 27   {
 28     Mp_pool_ = ::new (&Mp_Mem_) std::byte[M_block_size_*MAX_OBJECTS];
 29   }
 30
 31   ~allocator(){}
 32
 33   inline void* allocate(size_t n){
 34     auto cache_head = M_free_list_[n*M_block_size_];
 35     void* pblock = pop_from_pool(cache_head,n);
 36     if(!pblock)
 37     {
 38       if(M_pool_index_ <= M_max_objects_)
 39       {
 40         std::cout << "NEW! M_pool_index : M_block_in_use_ = " << M_pool_index_
 41           << " : " << M_block_in_use_ << '\n';
 42         pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
 43         M_pool_index_ += n;
 44       } else {
 45         std::cout << "M_pool_index = " << M_pool_index_ << '\n';
 46         assert(false);
 47       }
 48       M_block_in_use_ += n;
 49       M_size_map_[pblock] = n*M_block_size_;
 50     } else {
 51       std::printf("reuse pointer %p for size %lu\n",pblock,n*M_block_size_);
 52     }
 53     return pblock;
 54   }
 55
 56   inline void deallocate(void* memory, size_t )
 57   {
 58     auto chunk_size = M_size_map_[memory];
 59     if(chunk_size != 0){
 60       rewind(memory,chunk_size);
 61     }
 62   }
 63
 64 private:
 65   // 開放(deallocate)時に使用可能チャンクを更新
 66   void rewind(void* memory,size_t size){
 67     auto head = M_free_list_[size];
 68     block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
 69     pblock->next = head; //先頭ポインタを次にすすめる
 70     head = pblock;
 71     M_free_list_[size] = head;
 72     std::printf("rewind cache head %p for size %lu\n",head,size);
 73   }
 74   // 割当(allocate)時に開放された使用可能領域から使う
 75   void* pop_from_pool(block_not_in_use* head,size_t n){
 76     block_not_in_use* pblock = nullptr;
 77     if(head)
 78     {
 79       pblock = head;
 80       head = head->next;
 81     }
 82     M_free_list_[n*M_block_size_] = head;
 83     return static_cast<void*>(pblock);
 84   }
 85   // 使用可能なメモリータグ(一度割当、後に開放された領域)
 86   struct block_not_in_use {
 87     block_not_in_use *next;
 88   };
 89   size_map_t M_size_map_;
 90   free_list_t M_free_list_;
 91
 92   const size_t M_block_size_;
 93   const size_t M_max_objects_;
 94
 95   std::byte* Mp_pool_;
 96   unsigned M_pool_index_; //新規割当時にインクリメント、下がることはない
 97   unsigned M_block_in_use_;
 98   std::byte Mp_Mem_[sizeof(std::byte[N])*MAX_OBJECTS];
 99 };
100
101 /* スタックメモリーのブロックを割り当てるクラステンプレート */
102
103 template<typename T,size_t N>
104 class block_allocator
105 {
106 public:
107   typedef T* pointer;
108   typedef const T* const_pointer;
109   typedef T value_type;
110
111   template<typename U>
112   struct rebind
113   {
114     typedef block_allocator<U,N> other;
115   };
116
117   explicit block_allocator() noexcept {
118   }
119   ~block_allocator() {
120   }
121
122   template<typename U>
123   block_allocator(const block_allocator<U,N>&){}
124
125   template<typename U>
126   block_allocator(const std::allocator<U>&){}
127
128   T* allocate(std::size_t n)
129   {
130     return reinterpret_cast<T*>(S_alloc_.allocate(n));
131   }
132
133   void deallocate(T* p,size_t n)
134   {
135     S_alloc_.deallocate(static_cast<void*>(p),n);
136   }
137
138 private:
139   static allocator<T,N> S_alloc_;
140 };
141
142 template<typename T,size_t N>
143 allocator<T,N> block_allocator<T,N>::S_alloc_;
144
145 /* voidパラメーターのエラーチェックのためにvoid型のクラステンプレートを用意 */
146
147 template <size_t N>
148 class block_allocator<void,N>
149 {
150 public:
151   typedef void* pointer;
152   typedef const void* const_pointer;
153   typedef void value_type;
154
155   template<typename U>
156   struct rebind
157   {
158     typedef block_allocator<U,N> other;
159   };
160
161   explicit block_allocator() noexcept {
162   }
163
164   template<typename U>
165   block_allocator(const block_allocator<U,N>&){}
166
167   template<typename U>
168   block_allocator(std::allocator<U>&){}
169
170 };
171
172 struct A{
173   A(int x, int y, int z, int a) :
174     x_(x),
175     y_(y),
176     z_(z),
177     a_(a)
178   {}
179   int x_,y_,z_,a_;
180 };
181
182 int main()
183 {
184   int *ptr[10];
185
186   block_allocator<int,16> ba;
187
188   std::cout << "size == 1" << '\n';
189   for(int i = 0; i < 5; ++i){
190     ptr[i] = ba.allocate(1);
191   }
192   for(int i = 1; i < 5; ++i){
193     ba.deallocate(ptr[i],1);
194   }
195   for(int i = 5; i < 10; ++i){
196     ptr[i] = ba.allocate(1);
197   }
198   std::cout << "size == 2" << '\n';
199   for(int i = 1; i < 5; ++i){
200     ptr[i] = ba.allocate(2);
201   }
202   for(int i = 1; i < 5; ++i){
203     ba.deallocate(ptr[i],2);
204   }
205   for(int i = 1; i < 5; ++i){
206     ptr[i] = ba.allocate(2);
207   }
208 }

ビルドと実行結果. 

$ g++ main.cpp -std=c++17
$ ./a.out
size == 1
NEW! M_pool_index : M_block_in_use_ = 0 : 0
NEW! M_pool_index : M_block_in_use_ = 1 : 1
NEW! M_pool_index : M_block_in_use_ = 2 : 2
NEW! M_pool_index : M_block_in_use_ = 3 : 3
NEW! M_pool_index : M_block_in_use_ = 4 : 4
rewind cache head 0x56354f80a1d0 for size 8
rewind cache head 0x56354f80a1d8 for size 8
rewind cache head 0x56354f80a1e0 for size 8
rewind cache head 0x56354f80a1e8 for size 8
reuse pointer 0x56354f80a1e8 for size 8
reuse pointer 0x56354f80a1e0 for size 8
reuse pointer 0x56354f80a1d8 for size 8
reuse pointer 0x56354f80a1d0 for size 8
NEW! M_pool_index : M_block_in_use_ = 5 : 5
size == 2
NEW! M_pool_index : M_block_in_use_ = 6 : 6
NEW! M_pool_index : M_block_in_use_ = 8 : 8
NEW! M_pool_index : M_block_in_use_ = 10 : 10
NEW! M_pool_index : M_block_in_use_ = 12 : 12
rewind cache head 0x56354f80a1f8 for size 16
rewind cache head 0x56354f80a208 for size 16
rewind cache head 0x56354f80a218 for size 16
rewind cache head 0x56354f80a228 for size 16
reuse pointer 0x56354f80a228 for size 16
reuse pointer 0x56354f80a218 for size 16
reuse pointer 0x56354f80a208 for size 16
reuse pointer 0x56354f80a1f8 for size 16

このアロケーターは allocator クラステンプレートに実装していますね。

 11 #define MAX_OBJECTS 32
 12
 13 template<typename T,size_t N>
 14 class allocator
 15 {
 16   struct block_not_in_use;
 17   typedef std::map<void*,size_t> size_map_t;
 18   typedef std::map<size_t,block_not_in_use*> free_list_t;
 19   static_assert(sizeof(std::byte[N]) >= sizeof(T));
 20 public:
 21
 22   allocator()
 23     :
 24     M_max_objects_(MAX_OBJECTS),
 25     M_pool_index_(0),
 26     M_block_size_(sizeof(std::byte[N]) < 8 ? sizeof(long long) : sizeof(std::byte[N]))
 27   {
 28     Mp_pool_ = ::new (&Mp_Mem_) std::byte[M_block_size_*MAX_OBJECTS];
 29   }
 30
 31   ~allocator(){}
 32

 //   中略

 85   // 使用可能なメモリータグ(一度割当、後に開放された領域)
 86   struct block_not_in_use {
 87     block_not_in_use *next;
 88   };
 89   size_map_t M_size_map_;
 90   free_list_t M_free_list_;
 91
 92   const size_t M_block_size_;
 93   const size_t M_max_objects_;
 94
 95   std::byte* Mp_pool_;
 96   unsigned M_pool_index_; //新規割当時にインクリメント、下がることはない
 97   unsigned M_block_in_use_;
 98   std::byte Mp_Mem_[sizeof(std::byte[N])*MAX_OBJECTS];
 99 };

最初に見ておきたいのが以下の 5 つの変数・ポインターです。

Mp_mem_
スタック記憶域
Mp_pool_
スタックのメモリープール
M_max_objects
割り当て可能な最大オブジェクト数
M_pool_index_
メモリープールのインデックス
M_block_size_
アロケーターが割り当てるブロック(最少単位)のサイズ

これらのオブジェクト・変数はコンストラクターで初期化されています。

 22   allocator()
 23     :
 24     M_max_objects_(MAX_OBJECTS),
 25     M_pool_index_(0),
 26     M_block_size_(sizeof(std::byte[N]) < 8 ? sizeof(long long) : sizeof(std::byte[N]))
 27   {
 28     Mp_pool_ = ::new (&Mp_Mem_) std::byte[M_block_size_*MAX_OBJECTS];
 29   }

Mp_pool_ は Mp_mem_ のアドレスにある std::byte[] 配列を指すポインターとなり、これをメモリープールと呼びます。

メモリープール Mp_pool_ は単に割り当て可能なメモリー領域となります。

そして MAX_OBJECTS は任意の値ですが 32 個になってますね。

 11 #define MAX_OBJECTS 32

これは最大オブジェクト数を意味します。

 24     M_max_objects_(MAX_OBJECTS),

したがって M_max_objects が最大オブジェクト「数」ってことです。

M_block_size_ はブロックのバイトサイズなので個数じゃないので混同しないでくださいね。

ブロックサイズはオブジェクトのサイズである「sizeof(T)」以上に念の為にとっておきます。

 19   static_assert(sizeof(std::byte[N]) >= sizeof(T));

さらにポインター型をサポートしたいのでブロックサイズは 8 バイト以上に強制的に設定しておきます。

 26     M_block_size_(sizeof(std::byte[N]) < 8 ? sizeof(long long) : sizeof(std::byte[N]))

もっと小さい値を使う場合にしても、後で STL コンテナと使う場合には reserve() 関数を使う std::vector 等では コンテナが勝手に使い方を決めてしまうので、かなり無駄になるかも知りませんね…

(´・ω・`)

後はメモリープールはこの 2 つを使って定義します。

 28     Mp_pool_ = ::new (&Mp_Mem_) std::byte[M_block_size_*MAX_OBJECTS];

voidポインターでなく、std::byte ポインターをベースに使うのはポインター算術が使えるからです。

まあ char ポインターでも良いんですけどね…

ちなみにスタックアロケーターはヒープアロケーターと比べ大容量に弱いので MAX_OBJECTS または M_max_objects はあまり巨大な数値にするのはやめときましょう。

後はメモリーを解放後に管理するためのキャッシュ記憶域ですが、これは単に単方向の連結リストを使います。

 85   // 使用可能なメモリータグ(一度割当、後に開放された領域)
 86   struct block_not_in_use {
 87     block_not_in_use *next;
 88   };

struct block_not_in_use は一つのポインターを持つだけの連結リストとして、フリーチャンクを保持します。

具体には deallocate() をコールするたびに以下のように先頭ポインターを先に進めることで使います。

先頭を更新していきます。

 65   // 開放(deallocate)時に使用可能チャンクを更新
 66   void rewind(void* memory,size_t size){
 67     auto head = M_free_list_[size];
 68     block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
 69     pblock->next = head; //先頭ポインタを次にすすめる
 70     head = pblock;
 71     M_free_list_[size] = head;
 72     std::printf("rewind cache head %p for size %lu\n",head,size);
 73   }

 この関数によって用済みになったメモリーをリワインド(巻き戻す)していきます。

 正確には最後にキャッシュ( struct block_not_in_use* )にいれたチャンクが先頭( head ) になるので、巻き戻すというより押し込むの方が正しそうですね…

 (´・ω・`)

 まあ慣れちゃって癖なので気にしないヨ…

 次にフリーチャンクを再割り当て、再利用に使うための関数です。

 74   // 割当(allocate)時に開放された使用可能領域から使う
 75   void* pop_from_pool(block_not_in_use* head,size_t n){
 76     block_not_in_use* pblock = nullptr;
 77     if(head)
 78     {
 79       pblock = head;
 80       head = head->next;
 81     }
 82     M_free_list_[n*M_block_size_] = head;
 83     return static_cast<void*>(pblock);
 84   }

 これは allocate() 関数からコールする関数ですが head (先頭)にフリーチャンクが存在するなら、それを pblock に代入して head を連結リストから取り出し、直前の head を現在の head に更新します。

 77     if(head)
 78     {
 79       pblock = head;
 80       head = head->next;
 81     }

 後は更新されたであろう head をフリーリストにいれておきます。

 82     M_free_list_[n*M_block_size_] = head;

 M_free_list_ の型は free_list_t となり以下のように定義されています。

 18   typedef std::map<size_t,block_not_in_use*> free_list_t;

 まあつまり、重複なしのマップデータ構造となりますが、すでに存在したら何もしないですし、存在するアドレスならデータ構造に挿入されます。

 まあいきなりなので補足しますね。

 89   size_map_t M_size_map_;
 90   free_list_t M_free_list_;

 この 2 つは以下のような定義になっています。

 17   typedef std::map<void*,size_t> size_map_t;
 18   typedef std::map<size_t,block_not_in_use*> free_list_t;

 free_list_t は各リクエストサイズごとにフリーチャンクのリスト( block_no_in_use* )をキープします。

 size_map_t は各フリーチャンクのリクエストサイズをキープします。

M_size_map_
各リクエストサイズごとにフリーチャンクのリストを持つデータオブジェクト
M_free_list_
各フリーチャンクに対してリクエストサイズを持つデータオブジェクト

 この 2 つのオブジェクトは一見不要に見えますが、必須です。

 これは allocate() 関数の宣言を見れば分かります。

 33   inline void* allocate(size_t n){

 allocate() 関数はリクエスト個数( n ) * ブロックサイズ( M_block_size_ )をリクエストサイズとするので、毎回リクエストサイズが同じとは限らないです。

 ということは解放したチャンクサイズはブロックサイズ 1 個分、ブロックサイズ 2 個分、ブロックサイズ 3 個分というように異なるサイズとなるために、フリーチャンクのサイズは一定せず変動します。

 33   inline void* allocate(size_t n){
 34     auto cache_head = M_free_list_[n*M_block_size_];
 35     void* pblock = pop_from_pool(cache_head,n);
 36     if(!pblock)
 37     {
 38       if(M_pool_index_ <= M_max_objects_)
 39       {
 40         std::cout << "NEW! M_pool_index : M_block_in_use_ = " << M_pool_index_
 41           << " : " << M_block_in_use_ << '\n';
 42         pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
 43         M_pool_index_ += n;
 44       } else {
 45         std::cout << "M_pool_index = " << M_pool_index_ << '\n';
 46         assert(false);
 47       }
 48       M_block_in_use_ += n;
 49       M_size_map_[pblock] = n*M_block_size_;
 50     } else {
 51       std::printf("reuse pointer %p for size %lu\n",pblock,n*M_block_size_);
 52     }
 53     return pblock;
 54   }

 allocate() 関数は必要以上に長いように見えますが、スタックの再利用を実装するならば、これぐらいの行数は普通に使います。

 まずはキャッシュの先頭を取得して、そのキャッシュヘッドから使えるフリーチャンクを取り出します。

 34     auto cache_head = M_free_list_[n*M_block_size_];
 35     void* pblock = pop_from_pool(cache_head,n);

 この際に pop_from_pool() にはリクエスト個数 n を指定していますね。

 これはキャッシュから「リクエスト個数( n ) × ブロックサイズ( M_block_size_ )」のリクエストサイズに一致するフリーチャンクをポップするために必要な引数です。

 pblock が NULL 値でないのなら allocate() 関数は pblock をそのまま返します。

 それで pblock が NULL の場合の allocate() 関数の続きです。

 38       if(M_pool_index_ <= M_max_objects_)
 39       {
 40         std::cout << "NEW! M_pool_index : M_block_in_use_ = " << M_pool_index_
 41           << " : " << M_block_in_use_ << '\n';
 42         pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
 43         M_pool_index_ += n;
 44       } else {
 45         std::cout << "M_pool_index = " << M_pool_index_ << '\n';
 46         assert(false);
 47       }
 48       M_block_in_use_ += n;
 49       M_size_map_[pblock] = n*M_block_size_;

 pblock はメモリープールこと Mp_pool_ を起点にして「プールインデックス × ブロックサイズ」を加算した void ポインターアドレスを代入します。

 42         pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));

 M_pool_index_ は「次に使用するブロック数」を意味します。

 M_pool_index_ は pblock に割り当てた時点で「使用中のブロック数」に意味を転じます。

 つまり使用中のブロック数とブロックサイズを乗じることで、次に割り当てたいブロックのアドレスを算出します。

 割り当てアドレスを算出後には以下のようにアップデートします。

 43         M_pool_index_ += n;

 最後に割り当てたブロック情報のブックキーピングをしておきます。

 48       M_block_in_use_ += n;
 49       M_size_map_[pblock] = n*M_block_size_;

 M_block_in_use_ は使用中のブロック数で M_pool_index_ と同じ値となりますが、名前が紛らわしいので、もう一つ変数を保持することにしただけです。

 M_size_map_ の行は pblock のアドレスとリクエストサイズを紐付けるマップデータを設定していますね。

 最後に割り当て済みチャンクを解放する deallocate() 関数です。

 56   inline void deallocate(void* memory, size_t )
 57   {
 58     auto chunk_size = M_size_map_[memory];
 59     if(chunk_size != 0){
 60       rewind(memory,chunk_size);
 61     }
 62   }

 最初にやるのは M_size_map_ から memory に一対のチャンクサイズを取得します。

 58     auto chunk_size = M_size_map_[memory];

 次に該当するメモリーアドレスとチャンクサイズが存在するのであれば rewind() 関数をコールします。

 59     if(chunk_size != 0){
 60       rewind(memory,chunk_size);
 61     }

 これによってチャンクをフリーリストに登録して、メモリーを再利用のキャッシュとして使えるようにします。

 最後にテストコードもチェックしてみましょうかね。

182 int main()
183 {
184   int *ptr[10];
185
186   block_allocator<int,16> ba;
187
188   std::cout << "size == 1" << '\n';
189   for(int i = 0; i < 5; ++i){
190     ptr[i] = ba.allocate(1);
191   }
192   for(int i = 1; i < 5; ++i){
193     ba.deallocate(ptr[i],1);
194   }
195   for(int i = 5; i < 10; ++i){
196     ptr[i] = ba.allocate(1);
197   }
198   std::cout << "size == 2" << '\n';
199   for(int i = 1; i < 5; ++i){
200     ptr[i] = ba.allocate(2);
201   }
202   for(int i = 1; i < 5; ++i){
203     ba.deallocate(ptr[i],2);
204   }
205   for(int i = 1; i < 5; ++i){
206     ptr[i] = ba.allocate(2);
207   }
208 }

 リクエストするオブジェクト数を 1 個と 2 個のケースでテストしていますね。

 オブジェクト数 1 個のケースでは 5 回割り当てた後に 4 回解放します。

 さらにその後に追加で 5 回割り当てをします。

189   for(int i = 0; i < 5; ++i){
190     ptr[i] = ba.allocate(1);
191   }
192   for(int i = 1; i < 5; ++i){
193     ba.deallocate(ptr[i],1);
194   }
195   for(int i = 5; i < 10; ++i){
196     ptr[i] = ba.allocate(1);
197   }

 4 個分のフリーチャンクができるので、新規割り当て回数はこの場合は 6 回となりますね。

 これは出力を見ると分かるかと思います。

size == 1
NEW! M_pool_index : M_block_in_use_ = 0 : 0
NEW! M_pool_index : M_block_in_use_ = 1 : 1
NEW! M_pool_index : M_block_in_use_ = 2 : 2
NEW! M_pool_index : M_block_in_use_ = 3 : 3
NEW! M_pool_index : M_block_in_use_ = 4 : 4
rewind cache head 0x56354f80a1d0 for size 8
rewind cache head 0x56354f80a1d8 for size 8
rewind cache head 0x56354f80a1e0 for size 8
rewind cache head 0x56354f80a1e8 for size 8
reuse pointer 0x56354f80a1e8 for size 8
reuse pointer 0x56354f80a1e0 for size 8
reuse pointer 0x56354f80a1d8 for size 8
reuse pointer 0x56354f80a1d0 for size 8
NEW! M_pool_index : M_block_in_use_ = 5 : 5

 オブジェクト数 2 個のケースでは 4 回割り当てた後に 4 回解放し、さらにもう一回割り当てします。

199   for(int i = 1; i < 5; ++i){
200     ptr[i] = ba.allocate(2);
201   }
202   for(int i = 1; i < 5; ++i){
203     ba.deallocate(ptr[i],2);
204   }
205   for(int i = 1; i < 5; ++i){
206     ptr[i] = ba.allocate(2);
207   }

 これも出力で正しく実装されていることが確認できます。

size == 2
NEW! M_pool_index : M_block_in_use_ = 6 : 6
NEW! M_pool_index : M_block_in_use_ = 8 : 8
NEW! M_pool_index : M_block_in_use_ = 10 : 10
NEW! M_pool_index : M_block_in_use_ = 12 : 12
rewind cache head 0x56354f80a1f8 for size 16
rewind cache head 0x56354f80a208 for size 16
rewind cache head 0x56354f80a218 for size 16
rewind cache head 0x56354f80a228 for size 16
reuse pointer 0x56354f80a228 for size 16
reuse pointer 0x56354f80a218 for size 16
reuse pointer 0x56354f80a208 for size 16
reuse pointer 0x56354f80a1f8 for size 16

 リクエストサイズは「ブロックサイズ × 2 = 16 バイト」になってますね。

 ただこのケースでは一定サイズを超えるとエラーを投げるようにしています。

 38       if(M_pool_index_ <= M_max_objects_)
 39       {

 //  中略

 44       } else {
 45         std::cout << "M_pool_index = " << M_pool_index_ << '\n';
 46         assert(false);
 47       }

 これを解決する方法は次に項目で解説したいと思います。

Copyright 2018-2019, by Masaki Komatsu