手写移植SGI STL二级空间配置器内存池

笔者建议配合这两篇博客进行学习

侯捷 | C++ | 内存管理 | 学习笔记(二):第二章节 std::allocator-CSDN博客

施磊C++ | 项目实战 | SGI STL二级空间配置器源码剖析-CSDN博客

考虑的问题:多线程安全

空间配置器是容器使用的,而容器产生的对象是很有可能在多个线程中去操作的

1.大致框架

1.四个函数定义

2.重要的类型变量

3.两个辅助函数

4.静态成员函数初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#pragma once
#include<mutex>
//移植SGI STL二级空间配置器内存池 源码

template<typename T>
class myallocator
{
public:
//开辟内存 __n是开辟的大小(多少字节)
T* allocate(size_t __n);
//释放内存 __n是释放的大小
void deallocate(void* __p, size_t __n);

//内存扩容或缩容
void* reallocate(void* __P, size_t __old_sz, size_t __new_sz);

//对象构造 定位new实现
void construct(T *__p,const T&val)
{
new (__p) T(val);
}

//对象析构
void destroy(T* __p)
{
__p->~T();
}
private:
//自由链表是从8字节开始,以8字节为对齐方式,扩充到128字节
//obj数组的第一个链表挂着的是8字节,第二个是16字节,一次类推到128字节
enum { _ALIGN = 8 };
//内存池最大的chunk块的大小
enum { _MAX_BYTES = 128 };
//自由链表的数量 16 = 128 / 8
enum { _NFREELISTS = 16 };

// 每一个内存chunk块(结点)的头信息
union _Obj {
union _Obj* _M_free_list_link;//保存下一个结点的地址
char _M_client_data[1];
};

// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为 0
/*多线程环境下防止线程自己拷贝一个副本,会加一个volatile关键字,
使得在数据段或者堆上的数据改变了每个线程都可以立马看到,
防止多个线程看到的数据版本不一致*/
static _Obj* volatile _S_free_list[_NFREELISTS];//s_free_list存储自由链表数组的地址,这是个数组名,大小就是上面的16

//内存池基于freelist实现,需要考虑线程安全,主要做互斥操作 换成C++11的锁
static std::mutex mtx;


//已分配的内存chunk块的使用情况 初始化全为0
//start~~end 分配给程序之后剩下的空闲内存起始和终止地址 heapsize 堆的大小,记录的是已经分配的内存大小 除以16算出来的就是追加量
static char* _S_start_free;
static char* _S_end_free;
static size_t _S_heap_size;

/*将 __bytes 上调至最邻近的 8 的倍数 _ALIGN==8
1-8 全都映射(对齐)到8
9-16 全都映射(对齐)到16
*/
static size_t _S_round_up(size_t __bytes)
{
return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
}

/*返回 __bytes 大小的chunk块位于 free-list 中的编号
作用就是,如果申请的字节在1-8之间,就挂到内存块大小为8的那个链表下面
如果申请的字节在9-16之间,就挂到内存块大小为16的那个链表下面
*/
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
}

//把分配好的chunk进行连接的
static void* _S_refill(size_t __n);

//主要负责分配自由链表,chunk块的
static char* _S_chunk_alloc(size_t __size,int& __nobjs);
};

//静态成员变量初始化
template <typename T>
char* myallocator<T>::_S_start_free = nullptr;
template <typename T>
char* myallocator<T>::_S_end_free = nullptr;
template <typename T>
size_t myallocator<T>::_S_heap_size = 0;


template <typename T>
//这里的typename告诉编译器这里是一个类型定义
typename myallocator<T>::_Obj* volatile myallocator<T>::_S_free_list[_NFREELISTS]=
{nullptr,nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};

template <typename T>
std::mutex myallocator<T>::mtx;

2.allocate函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//开辟内存  __n是开辟的大小(多少字节)
T* allocate(size_t __n)
{
//要传入的是字节数,而容器调用传进来的1,2,3这些数字是个数不是字节数,还要乘以T的大小才行
__n = __n * sizeof(T);

void* __ret = 0;
//大于128字节,直接malloc
if (__n > (size_t)_MAX_BYTES) {
__ret = malloc_alloc::allocate(__n);
}
//小于128字节
else {
_Obj* volatile* __my_free_list
= _S_free_list + _S_freelist_index(__n);

//线程安全
std::lock_guard<std::mutex> guard(mtx);

_Obj* __result = *__my_free_list;
//内存池有chunk块就去相应大小的链表下面拿,没有的话就看如何进行分配了
if (__result == 0)//没有相应大小chunk块,下面解释如何分配
__ret = _S_refill(_S_round_up(__n));
else {//有相应大小chunk块,就从内存池里面拿出相应的块分配给程序
*__my_free_list = __result->_M_free_list_link;
__ret = __result;
}
}
//返回我们从内存池申请的chunk块
return (T*)__ret;
}

注意点:

1.vector容器传入的__n是对象个数,还要乘以对象类型T的大小才是我们要开辟的字节数

2.最后指针要强转

3.把线程安全换成c++11的互斥锁

3.refill函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//把分配好的chunk进行连接的
static void* _S_refill(size_t __n)
{
int __nobjs = 20;
//chunk_alloc主要负责内存开辟
char* __chunk = _S_chunk_alloc(__n, __nobjs);
_Obj* volatile* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;

if (1 == __nobjs) return(__chunk);
__my_free_list = _S_free_list + _S_freelist_index(__n);

/* Build free list in chunk */
//result和chunk都指向chunk_alloc分配的内存的首地址 chunk指针用来遍历chunk块,result指针指向开头的地方做一个保存
__result = (_Obj*)__chunk;
//把头指针的位置指向下一chunk块,因为当前的情形是已经要把当前自由链表头指针的位置分配出去了,而头指针的后面才是剩下的空闲的chunk块,再次分配是分配空闲的,分配出去的就没关系了
*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
for (__i = 1; ; __i++) {
__current_obj = __next_obj;
//这行描述的就是遍历过程 先转成char*,这样每次加减都是以字节为单位,chunk指针每一次加8字节到下一个chunk块(不一定是8,这里只是说明,具体多少要看内存块的大小,反正就是到下一个chunk内存块了)
__next_obj = (_Obj*)((char*)__next_obj + __n);
if (__nobjs - 1 == __i) {
__current_obj->_M_free_list_link = 0;
break;
}
else {
__current_obj->_M_free_list_link = __next_obj;
}
}
//返回第一个空闲的chunk结点给容器使用
return(__result);
}

4._S_chunk_alloc函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//主要负责分配自由链表,chunk块的
static char* _S_chunk_alloc(size_t __size,int& __nobjs)
{
char* __result;
size_t __total_bytes = __size * __nobjs;
size_t __bytes_left = _S_end_free - _S_start_free;

if (__bytes_left >= __total_bytes) {
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
else if (__bytes_left >= __size) {
__nobjs = (int)(__bytes_left / __size);
__total_bytes = __size * __nobjs;
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
else {
size_t __bytes_to_get =
2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
// Try to make use of the left-over piece.
if (__bytes_left > 0) {
_Obj* volatile* __my_free_list =
_S_free_list + _S_freelist_index(__bytes_left);

((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;
*__my_free_list = (_Obj*)_S_start_free;
}
_S_start_free = (char*)malloc(__bytes_to_get);
if (nullptr == _S_start_free) {
size_t __i;
_Obj* volatile* __my_free_list;
_Obj* __p;
// Try to make do with what we have. That can't
// hurt. We do not try smaller requests, since that tends
// to result in disaster on multi-process machines.
for (__i = __size;
__i <= (size_t)_MAX_BYTES;
__i += (size_t)_ALIGN) {
__my_free_list = _S_free_list + _S_freelist_index(__i);
__p = *__my_free_list;
if (0 != __p) {
*__my_free_list = __p->_M_free_list_link;
_S_start_free = (char*)__p;
_S_end_free = _S_start_free + __i;
return(_S_chunk_alloc(__size, __nobjs));
// Any leftover piece will eventually make it to the
// right free list.
}
}
_S_end_free = 0; // In case of exception.
_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
// This should either throw an
// exception or remedy the situation. Thus we assume it
// succeeded.
}
_S_heap_size += __bytes_to_get;
_S_end_free = _S_start_free + __bytes_to_get;
return(_S_chunk_alloc(__size, __nobjs));
}
}

5.malloc_alloc::allocate函数

最后一行typedef __malloc_alloc_template<0> malloc_alloc;

这个类就是malloc_alloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//封装了malloc和free函数,可以设置OOM释放内存的回调函数
template <int __inst>
class __malloc_alloc_template {

private:

static void* _S_oom_malloc(size_t);
static void* _S_oom_realloc(void*, size_t);
static void (*__malloc_alloc_oom_handler)();

public:

static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}

static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}

static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}

static void (*__set_malloc_handler(void (*__f)()))()
{
void (*__old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = __f;
return(__old);
}

};

template <int __inst>
void (*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;

template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
void (*__my_malloc_handler)();
void* __result;

for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { throw std::bad_alloc(); }
(*__my_malloc_handler)();
__result = malloc(__n);
if (__result) return(__result);
}
}

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
void (*__my_malloc_handler)();
void* __result;

for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { throw std::bad_alloc(); }
(*__my_malloc_handler)();
__result = realloc(__p, __n);
if (__result) return(__result);
}
}

typedef __malloc_alloc_template<0> malloc_alloc;

6.deallocate函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//释放内存  __n是释放的大小
void deallocate(void* __p, size_t __n)
{
//回收的过大 直接用free
if (__n > (size_t)_MAX_BYTES)
malloc_alloc::deallocate(__p, __n);
else {
//定位到要回收的chunk块的地址
_Obj* volatile* __my_free_list
= _S_free_list + _S_freelist_index(__n);
_Obj* __q = (_Obj*)__p;

std::lock_guard<std::mutex> guard(mtx);

//这里就是链表的头插法
//要归还的块是要插入的块,要插入到头结点和头结点的next域指的节点(这个节点是第一个空闲块),而我们归还的这个块就接到头结点的next域,然后要回收的块再接着原来的第一个空闲块,成为新的第一个空闲块,就类比一下链表的头插法就行
__q->_M_free_list_link = *__my_free_list;
*__my_free_list = __q;
// lock is released here 出作用域锁释放
}
}

7.reallocate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//内存扩容或缩容
void* reallocate(void* __p, size_t __old_sz, size_t __new_sz)
{
void* __result;
size_t __copy_sz;
//如果旧的和新的全都比128字节大,那说明这就不是从我内存池里面出去的,那就直接调用c的realloc函数
if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {
return(realloc(__p, __new_sz));
}
//如果新的和旧的对齐以后一样,那就不用扩容,直接返回 比如一个12,扩容到15,但是这两个的round_up都是16 那就没必要扩容了
if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
//分配一块新的大小的内存空间
__result = allocate(__new_sz);
//比较大小,就选个比较小的
__copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;
//把内容copy到新开辟的内存空间
memcpy(__result, __p, __copy_sz);
//把旧的内存空间给释放掉
deallocate(__p, __old_sz);
//最后把新的内存地址返回
return(__result);
}

8.STL容器要求配置器里面必须有的东西

1
2
3
4
5
6
7
8
9
10
11
12
//vector容器里面要用的东西,不写就会报错
using value_type = T;

//模仿写vector的allocator的构造和拷贝构造 不写也会报错的
constexpr myallocator() noexcept
{ // construct default allocator (do nothing)
}
constexpr myallocator(const myallocator&) noexcept = default;
template<class _Other>
constexpr myallocator(const myallocator<_Other>&) noexcept
{ // construct from a related allocator (do nothing)
}

9.完整版myallocator.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#pragma once
#include<mutex>
#include<iostream>
//移植SGI STL二级空间配置器内存池 源码

//封装了malloc和free函数,可以设置OOM释放内存的回调函数
template <int __inst>
class __malloc_alloc_template {

private:

static void* _S_oom_malloc(size_t);
static void* _S_oom_realloc(void*, size_t);
static void (*__malloc_alloc_oom_handler)();

public:

static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}

static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}

static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}

static void (*__set_malloc_handler(void (*__f)()))()
{
void (*__old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = __f;
return(__old);
}

};

template <int __inst>
void (*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;

template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
void (*__my_malloc_handler)();
void* __result;

for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { throw std::bad_alloc(); }
(*__my_malloc_handler)();
__result = malloc(__n);
if (__result) return(__result);
}
}

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
void (*__my_malloc_handler)();
void* __result;

for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { throw std::bad_alloc(); }
(*__my_malloc_handler)();
__result = realloc(__p, __n);
if (__result) return(__result);
}
}

typedef __malloc_alloc_template<0> malloc_alloc;


template<typename T>
class myallocator
{
public:
//vector容器里面要用的东西,不写就会报错
using value_type = T;

//模仿写vector的allocator的构造和拷贝构造 不写也会报错的
constexpr myallocator() noexcept
{ // construct default allocator (do nothing)
}
constexpr myallocator(const myallocator&) noexcept = default;
template<class _Other>
constexpr myallocator(const myallocator<_Other>&) noexcept
{ // construct from a related allocator (do nothing)
}

//开辟内存 __n是开辟的大小(多少字节)
T* allocate(size_t __n)
{
//要传入的是字节数,而容器调用传进来的1,2,3这些数字是个数不是字节数,还要乘以T的大小才行
__n = __n * sizeof(T);

void* __ret = 0;
//大于128字节,直接malloc
if (__n > (size_t)_MAX_BYTES) {
__ret = malloc_alloc::allocate(__n);
}
//小于128字节
else {
_Obj* volatile* __my_free_list
= _S_free_list + _S_freelist_index(__n);

//线程安全
std::lock_guard<std::mutex> guard(mtx);

_Obj* __result = *__my_free_list;
//内存池有chunk块就去相应大小的链表下面拿,没有的话就看如何进行分配了
if (__result == 0)//没有相应大小chunk块,下面解释如何分配
__ret = _S_refill(_S_round_up(__n));
else {//有相应大小chunk块,就从内存池里面拿出相应的块分配给程序
*__my_free_list = __result->_M_free_list_link;
__ret = __result;
}
}
//返回我们从内存池申请的chunk块
return (T*)__ret;
}
//释放内存 __n是释放的大小
void deallocate(void* __p, size_t __n)
{
//回收的过大 直接用free
if (__n > (size_t)_MAX_BYTES)
malloc_alloc::deallocate(__p, __n);
else {
//定位到要回收的chunk块的地址
_Obj* volatile* __my_free_list
= _S_free_list + _S_freelist_index(__n);
_Obj* __q = (_Obj*)__p;

std::lock_guard<std::mutex> guard(mtx);

//这里就是链表的头插法
//要归还的块是要插入的块,要插入到头结点和头结点的next域指的节点(这个节点是第一个空闲块),而我们归还的这个块就接到头结点的next域,然后要回收的块再接着原来的第一个空闲块,成为新的第一个空闲块,就类比一下链表的头插法就行
__q->_M_free_list_link = *__my_free_list;
*__my_free_list = __q;
// lock is released here 出作用域锁释放
}
}

//内存扩容或缩容
void* reallocate(void* __p, size_t __old_sz, size_t __new_sz)
{
void* __result;
size_t __copy_sz;
//如果旧的和新的全都比128字节大,那说明这就不是从我内存池里面出去的,那就直接调用c的realloc函数
if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {
return(realloc(__p, __new_sz));
}
//如果新的和旧的对齐以后一样,那就不用扩容,直接返回 比如一个12,扩容到15,但是这两个的round_up都是16 那就没必要扩容了
if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
//分配一块新的大小的内存空间
__result = allocate(__new_sz);
//比较大小,就选个比较小的
__copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;
//把内容copy到新开辟的内存空间
memcpy(__result, __p, __copy_sz);
//把旧的内存空间给释放掉
deallocate(__p, __old_sz);
//最后把新的内存地址返回
return(__result);
}

//对象构造 定位new实现
void construct(T *__p,const T&val)
{
new (__p) T(val);
}

//对象析构
void destroy(T* __p)
{
__p->~T();
}
private:
//自由链表是从8字节开始,以8字节为对齐方式,扩充到128字节
//obj数组的第一个链表挂着的是8字节,第二个是16字节,一次类推到128字节
enum { _ALIGN = 8 };
//内存池最大的chunk块的大小
enum { _MAX_BYTES = 128 };
//自由链表的数量 16 = 128 / 8
enum { _NFREELISTS = 16 };

// 每一个内存chunk块(结点)的头信息
union _Obj {
union _Obj* _M_free_list_link;//保存下一个结点的地址
char _M_client_data[1];
};

// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为 0
/*多线程环境下防止线程自己拷贝一个副本,会加一个volatile关键字,
使得在数据段或者堆上的数据改变了每个线程都可以立马看到,
防止多个线程看到的数据版本不一致*/
static _Obj* volatile _S_free_list[_NFREELISTS];//s_free_list存储自由链表数组的地址,这是个数组名,大小就是上面的16

//内存池基于freelist实现,需要考虑线程安全,主要做互斥操作
static std::mutex mtx;


//已分配的内存chunk块的使用情况 初始化全为0
//start~~end 分配给程序之后剩下的空闲内存起始和终止地址 heapsize 堆的大小,记录的是已经分配的内存大小 除以16算出来的就是追加量
static char* _S_start_free;
static char* _S_end_free;
static size_t _S_heap_size;

/*将 __bytes 上调至最邻近的 8 的倍数 _ALIGN==8
1-8 全都映射(对齐)到8
9-16 全都映射(对齐)到16
*/
static size_t _S_round_up(size_t __bytes)
{
return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
}

/*返回 __bytes 大小的chunk块位于 free-list 中的编号
作用就是,如果申请的字节在1-8之间,就挂到内存块大小为8的那个链表下面
如果申请的字节在9-16之间,就挂到内存块大小为16的那个链表下面
*/
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
}

//把分配好的chunk进行连接的
static void* _S_refill(size_t __n)
{
int __nobjs = 20;
//chunk_alloc主要负责内存开辟
char* __chunk = _S_chunk_alloc(__n, __nobjs);
_Obj* volatile* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;

if (1 == __nobjs) return(__chunk);
__my_free_list = _S_free_list + _S_freelist_index(__n);

/* Build free list in chunk */
//result和chunk都指向chunk_alloc分配的内存的首地址 chunk指针用来遍历chunk块,result指针指向开头的地方做一个保存
__result = (_Obj*)__chunk;
//把头指针的位置指向下一chunk块,因为当前的情形是已经要把当前自由链表头指针的位置分配出去了,而头指针的后面才是剩下的空闲的chunk块,再次分配是分配空闲的,分配出去的就没关系了
*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
for (__i = 1; ; __i++) {
__current_obj = __next_obj;
//这行描述的就是遍历过程 先转成char*,这样每次加减都是以字节为单位,chunk指针每一次加8字节到下一个chunk块(不一定是8,这里只是说明,具体多少要看内存块的大小,反正就是到下一个chunk内存块了)
__next_obj = (_Obj*)((char*)__next_obj + __n);
if (__nobjs - 1 == __i) {
__current_obj->_M_free_list_link = 0;
break;
}
else {
__current_obj->_M_free_list_link = __next_obj;
}
}
//返回第一个空闲的chunk结点给容器使用
return(__result);
}

//主要负责分配自由链表,chunk块的
static char* _S_chunk_alloc(size_t __size,int& __nobjs)
{
char* __result;
size_t __total_bytes = __size * __nobjs;
size_t __bytes_left = _S_end_free - _S_start_free;

if (__bytes_left >= __total_bytes) {
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
else if (__bytes_left >= __size) {
__nobjs = (int)(__bytes_left / __size);
__total_bytes = __size * __nobjs;
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
else {
size_t __bytes_to_get =
2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
// Try to make use of the left-over piece.
if (__bytes_left > 0) {
_Obj* volatile* __my_free_list =
_S_free_list + _S_freelist_index(__bytes_left);

((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;
*__my_free_list = (_Obj*)_S_start_free;
}
_S_start_free = (char*)malloc(__bytes_to_get);
if (nullptr == _S_start_free) {
size_t __i;
_Obj* volatile* __my_free_list;
_Obj* __p;
// Try to make do with what we have. That can't
// hurt. We do not try smaller requests, since that tends
// to result in disaster on multi-process machines.
for (__i = __size;
__i <= (size_t)_MAX_BYTES;
__i += (size_t)_ALIGN) {
__my_free_list = _S_free_list + _S_freelist_index(__i);
__p = *__my_free_list;
if (0 != __p) {
*__my_free_list = __p->_M_free_list_link;
_S_start_free = (char*)__p;
_S_end_free = _S_start_free + __i;
return(_S_chunk_alloc(__size, __nobjs));
// Any leftover piece will eventually make it to the
// right free list.
}
}
_S_end_free = 0; // In case of exception.
_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
// This should either throw an
// exception or remedy the situation. Thus we assume it
// succeeded.
}
_S_heap_size += __bytes_to_get;
_S_end_free = _S_start_free + __bytes_to_get;
return(_S_chunk_alloc(__size, __nobjs));
}
}

};

//静态成员变量初始化
template <typename T>
char* myallocator<T>::_S_start_free = nullptr;
template <typename T>
char* myallocator<T>::_S_end_free = nullptr;
template <typename T>
size_t myallocator<T>::_S_heap_size = 0;


template <typename T>
//这里的typename告诉编译器这里是一个类型定义
typename myallocator<T>::_Obj* volatile myallocator<T>::_S_free_list[_NFREELISTS]=
{nullptr,nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};

template <typename T>
std::mutex myallocator<T>::mtx;

10.pch.h和pch.cpp

1
2
3
4
5
6
//.h
#pragma once
#ifndef PCH_H
#define PCH_H

#endif
1
2
//.cpp
#include"pch.h"

11.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include<vector>
#include"pch.h"
#include"myallocator.h"

using namespace std;

int main()
{
vector<int, myallocator<int>> v;
for (int i = 0; i < 100; i++)
v.push_back(rand() % 1000);
for (int val : v)
cout << val << " ";
cout << endl;
return 0;
}

总结:

通过源码移植可以更加清楚内存池整个分配内存和释放的过程。

侯捷老师内存管理第二章和施磊老师的课程讲清楚了原理和流程。

施磊老师的手写移植内存池是进行实践。