本篇介绍程序库中的内存池算法。
内存池函数的声明文件为: young/youngc/yc_memory.h
内存池函数的实现文件为: young/youngc/yc_memory.c
**3.1**
首先来看一下内存池算法用到的一些类型和常量。
下面的类型和常量定义在头文件 yc_definition.h 内;
硬件字节类型 :ylib_byte_t;
下面的结构和常量声明于实现文件 yc_memory.c 内;
内存池属性:
DEFAULT_PAGE_SIZE: 内存页可供分配的字节数;
MEMORY_POOL_LISTS: 内存池包含的内存链数;
MEMORY_POOL_BUFFER: 内存池每个链的缓存大小;
MEMORY_POOL_MIN_BYTES: 内存池管理的最小字节数;
MEMORY_POOL_MAX_BYTES: 内存池管理的最大字节数。
内存块类型:
typedef struct memory_block
{
struct memory_block* next;
} memblk_t;
内存页类型:
typedef struct memory_page
{
struct memory_page* next; /* 下一个内存页 */
size_t count; /* 未分配的内存块数 */
memblk_t* free; /* 未分配的内存块链表 */
} mempage_t;
内存链类型:
typedef struct memory_list
{
size_t page_size; /* 内存页内可供分配的字节数 */
size_t useable; /* 未满的可供分配的内存页数 */
mempage_t* buffer[MEMORY_POOL_BUFFER]; /* 页面缓存 */
mempage_t* first; /* 第一个内存页 */
} memlist_t;
**3.2**
内存池原理图:
索引:0 1 2 3 ...... MEMORY_POOL_LISTS - 1
--------------------------------------------------------------------------
| page_size | | | | ...... | |
--------------------------------------------------------------------------
| useable |
-------------
| buffer |
-------------
| first |
-------------
|
|
| ---------
->| next |--
|--------| |
| count | |
|--------| |
| free | |
|--------| |
| memory | |
--------- |
|
-----------
|
| ---------
->| next | --> NULL
|--------|
| count |
|--------|
| free |
|--------|
| memory |
---------
实际上,内存池的全称应该是小内存块内存池,内存池管理的内存块的上限是
MEMORY_POOL_MAX_BYTES 个字节,超过这个数值的申请要求会被转调用至 MEMALLOC 函数,
正因为此,所以在释放内存的时候必须显示地指定要释放的内存块的大小,这样才能确保
正确地释放。
内存池是一个 memlist_t 指针类型的数组,数组的大小为 MEMORY_POOL_LISTS 数组内
的每个指针元素指向一条内存链,每个内存链由大小不同的内存页以单向链表的形式组成。
为了方便管理,凡是向内存池申请小于等于 MEMORY_POOL_MAX_BYTES 个字节的内存块都会
被自动调整为 MEMORY_POOL_ALIGN 的倍数,而每个内存链则管理着不同大小的内存块,可
用该公式计算:内存链管理的内存块大小 = MEMORY_POOL_MIN + 索引 * MEMORY_POOL_ALIGN。
由于每个内存链管理的内存块都很小,为了减少向系统申请内存的次数和内存碎片,内
存链的每个节点并不是内存块,而是一种比内存块大的多的被称为内存页的数据结构。内存
页由四部分组成:
1、next 是指向下一个内存页的指针;
2、count 是内存页中可供分配的内存块数;
3、free 是指向可分配内存块链表的指针;
4、内存页中用以分配内存块的缓存区,缓存大小由内存链的 page_size 成员决定。
**3.2**
分配功能的实现。
内存功能功能由函数 pool_alloc 实现,其声明如下:
void* pool_alloc( size_t bytes );
函数的参数是申请的动态内存字节数,返回值为一个指向动态内存首地址的空指针,
如果分配失败,返回的值为 NULL。
如前所述,内存池处理的内存块大小是有上限的,当申请的字节数大于阈值的时候,则
直接 MEMALLOC 函数,只有当申请的字节数小于等于阈值的时候才会使用内存池进行分配,
这个阈值就是前面提到的 MEMORY_POOL_MAX_BYTES。
为了便于分配,内存池不一定会刚好分配用户申请的那么大的内存,而是会对申请的
值进行一些调整。内存池不会分配小于 MEMORY_POOL_MIN_BYTES 的内存块,算法会将分配
的字节数调整至刚好不小于申请的字节数的 MEMORY_POOL_ALIGN 的倍数,默认管理的最小
内存块和对齐的字节数均为sizeof(void*),在32位的机器上其值为4。举几个例子:假设用
户申请的是14个字节,由于14不是4的倍数,算法自动将申请的字节数调整为刚好大于14的4
的倍数,也就是16。
在完成了字节对齐后,就可以确定由哪个内存链来进行分配,确定内存链后,先进入
该链的页面缓存,在页面缓存中寻找是否有可供使用的内存页。链的页面缓存是长度等于
MEMORY_POOL_BUFFER 的一个 mempage_t* 数组,对数组进行遍历,寻找第一个不等于 NULL
的值,这个值就是需要的内存页指针。需要注意的是,被分配的内存页是连在 mempage_t
下面的,所以在查找内存块时,需要对指针进行偏移操作,跳过 sizeof(mempage_t) 个字
节。
在找到了有空闲内存块的内存页后,将该内存页 free 指向的第一个空闲内存块取出,
作为返回的值,随后调整 free 指向第二个内存块。
但是缓存并不总是会有内存页的,当缓存为空时,就需要对链表进行遍历,以找到可用
的内存页。对链表的遍历并不是找到第一个可分配的内存页后就停止,这样做会让后面的分
配在不久之后又必须要遍历链表,来一趟不容易,不如来了以后多做点事,免得以后老是要
跑来爬楼梯。所以内存池的链表遍历实际上是一个整理操作,它会在遍历的过程中动态调整
链表的排列,把找到的可供分配的内存页逐一上调到链表的首位,这样遍历完成后,所有的
可分配内存页都被上调至了链表的前部,在调整的同时用找到的未满内存页重新填充页面缓
存。这里需要注意的是遍历的页面数,其实并不需要将链表这个遍历,在遍历的同时,用一
个无符号整数 count 记录遍历过的未满内存页,当 count == useable 的时候,表示后面已
经没有可供分配的内存页了,此时就可退出循环。
以上的行为均在 useable 大于 0 的时候发生,而当 useable 等于 0 的时候,既不需
要遍历页面缓存,也不需要遍历链表,直接调用底层动态内存分配函数 MEMALLOC 分配一个
内存页,将分配的内存页挂在内存链的首位,同时放入页面缓存,再将可用内存分割成一个
个小内存块并串联起来,最后返回串联起来的内存块链表中的第一个内存块即可。需要注意
的一点是内存页内可供分配的字节数是由链表的 page_size 成员来指定的。page_size 初
始时等于 0,当进行内存页申请的时候,如果 page_size = 0,则计算该内存链下每个内存
页需占用的字节数。为了确保不浪费内存,page_size 的计算结果必须要能被当前内存链管
理的内存块大小整除,如果 DEFAULT_PAGE_SIZE % BLOCK_SIZE(index) = 0,则令 page_size
等于 DEFAULT_PAGE_SIZE,否则令 page_size等于 BLOCK_SIZE(index) 的整数倍。
可用如下代码测试内存池内所有内存链的属性:
for( i = 1; i <= 256; ++i )
pool_alloc(i);
pool_print();
**3.3**
回收功能的实现。
回收实际上分为两个操作,一个是回收,一个是释放。
回收操作由函数 pool_dealloc 实现,其声明如下:
void pool_dealloc( void* ptr, size_t bytes );
函数的第一个参数是指向要回收的内存块的指针,第二个参数是要回收的内存块的大小。
进入函数后,先判断指针是否为空,接着判断内存块的大小,如果大于内存池所能管理
的最大内存块,则直接调用 MEMFREE 将之释放;如果小于等于则有可能是由内存池分配出去
的。注意,只是有可能而已!
根据内存块的大小,先计算出内存块理论上所属的内存链,然后对该内存链进行遍历,
在遍历内存页的过程中,判断内存块的地址是否落在内存页的首地址和末地址之间,如果是
的话,则表示找到了内存块所属的内存页,如果没有,则继续遍历,如果遍历至链尾依然没
有找到,则表示该内存块不是由内存池分配的,直接调用 MEMFREE 将之释放。
在确定了内存块所属的内存页后,依然不能马上执行回收操作,还必须确定内存块的地
址是否正确。由于分配时,是以 BLOCK_SIZE(index) 来进行对齐的,所以回收时的地址也必
须能满足这个对齐要求,亦即满足条件:(块地址 - 页首地址) % BLOCK_SIZE(index) == 0,
验证无误后,方能执行回收操作。
回收的操作很简单,只需要将内存块回收至空闲块链表即可。随后将内存页调整至链首,
这样做一来可以方便下次回收,二来可以方便当页面缓存使用完后,对链表的快速遍历。
回收操作只是把回收的内存块重新放进内存池,并不会把空闲的内存页释放给系统。假
如应用程序向内存池申请了大量的内存而后由内存池回收,此时内存池的使用率接近 0%,而
其他的应用程序却因为大量内存被内存池占用而无法正常运行,此时需要将内存池占用的大
量空闲内存释放给系统以供其他应用程序使用。很显然,回收操作并不能胜任,这个工作必
须由释放操作来完成。
释放操作由函数 pool_free 实现,其声明如下:
void pool_free( void* ptr, size_t bytes );
函数的第一个参数是指向要回收的内存块的指针,第二个参数是要回收的内存块的大小。
释放操作分为两步,第一步直接调用 pool_dealloc 完成内存块的回收,第二步遍历整
个链表,在遍历的过程中将空闲的内存页释放给系统。注意,第一个参数是可以为 NULL 的,
此时,将跳过第一步,直接执行第二步。
为什么在释放操作里是对整个链表的遍历呢?因为我预期大部分的时候执行的都会是归
还操作,只有当系统内存紧张的时候才需要执行释放操作,此时释放操作执行一次即可满足
要求,接下来就又可以使用回收操作了。譬如,可以调用 get_pool_dealloc_count 函数了
解执行了回收的次数,当达到某个阈值的时候就可以调用 pool_free 释放一些空闲的内存页。
**3.4**
并发控制。
在多线程环境下,内存池的操作将会因线程之间的切换而崩溃!为此,内存池在实现的
时候提供了并行加锁和解锁操作。为了移植,加锁和解锁的实现交由用户来实现,内存池只
负责调度。
设置加锁函数:void set_pool_lock( void (*lock)(size_t index) )。
设置解锁函数:void set_pool_unlock( void (*unlock)(size_t index) )。
两个函数的参数都是一个声明原型如 void f(size_t) 的函数指针。这里解释一下传递
进来的加锁和解锁函数为什么需要一个 size_t index 参数。这个 index 参数就是内存链的
索引值,仔细观察一下就会发现,实际上每个内存链是相互独立的,内存池最多可以允许
MEMORY_POOL_LISTS 个在不同的链表内的线程同时操作。通过 index 这个参数,实现加锁和
解锁功能的用户就可以对不同的链表分别进行加锁和解锁。例如在 Windows 系统下,可以先
调用 get_pool_lists_count 函数以获取内存链的总数,然后创建一个互斥量数组,数组的大
小就等于内存链的总数,在实现的加锁和解锁函数里可以根据 index 参数决定是对哪个互斥
量进行操作。
如果用户没有设置这两个并发加锁、解锁函数,但是在 yc_configuration.h 中定义了
__MACRO_C_YOUNG_LIBRARY_OPERATING_SYSTEM_SUPPORT_POSIX_THREAD__ 宏,则程序库将自
动启用 POSIX 多线程互斥控制。如果以上两者用户均设置了,则程序库会优先调用用户传递
进来的并发控制函数。
**3.5**
模板化。
在 C++ 中使用内存池可以借助模板来进行一下包装。STL 中的内存分配器已经为我们
提供了一个样本。
模板化的内存池源码在 young/youngcpp/ycpp_memory.hpp 中。下面只列出三个主要的
成员函数。
pointer allocate( size_type n )
{
return (pointer)( youngc::pool_alloc( sizeof(T) * n ) );
}
void deallocate( pointer ptr, size_type n )
{
youngc::pool_dealloc( ptr, sizeof(T) * n );
}
~pool_allocator()
{
youngc::pool_free( NULL, sizeof(T) );
}
**3.6**
移植。
内存池的实现大部分都是不依赖于任何系统的标准 C 代码,需要修改的只是调用动态
内存分配和释放的两个系统函数,为了便于移植,将它们定义为了两个宏,在一些嵌入式系
统中可能需要重新定义:
#define MEMALLOC( bytes) malloc( bytes )
#define MEMFREE( ptr ) free( ptr )
另外一个可能会带来移植问题的函数是 pool_print,有些嵌入式系统的 C 编译器不支
持标准输入和输出,如果移植到这些平台的时候需要把这个函数注释掉。
若要修改内存池的属性,可修改 3.1 中介绍的几个属性枚举值。
在不需要并发编程对函数指针支持的又不好的硬件平台上,把 pool_lock 和 pool_unlock
函数指针以及 set_pool_lock 和 set_pool_unlock 函数注释掉即可。
内存池函数的声明文件为: young/youngc/yc_memory.h
内存池函数的实现文件为: young/youngc/yc_memory.c
**3.1**
首先来看一下内存池算法用到的一些类型和常量。
下面的类型和常量定义在头文件 yc_definition.h 内;
硬件字节类型 :ylib_byte_t;
下面的结构和常量声明于实现文件 yc_memory.c 内;
内存池属性:
DEFAULT_PAGE_SIZE: 内存页可供分配的字节数;
MEMORY_POOL_LISTS: 内存池包含的内存链数;
MEMORY_POOL_BUFFER: 内存池每个链的缓存大小;
MEMORY_POOL_MIN_BYTES: 内存池管理的最小字节数;
MEMORY_POOL_MAX_BYTES: 内存池管理的最大字节数。
内存块类型:
typedef struct memory_block
{
struct memory_block* next;
} memblk_t;
内存页类型:
typedef struct memory_page
{
struct memory_page* next; /* 下一个内存页 */
size_t count; /* 未分配的内存块数 */
memblk_t* free; /* 未分配的内存块链表 */
} mempage_t;
内存链类型:
typedef struct memory_list
{
size_t page_size; /* 内存页内可供分配的字节数 */
size_t useable; /* 未满的可供分配的内存页数 */
mempage_t* buffer[MEMORY_POOL_BUFFER]; /* 页面缓存 */
mempage_t* first; /* 第一个内存页 */
} memlist_t;
**3.2**
内存池原理图:
索引:0 1 2 3 ...... MEMORY_POOL_LISTS - 1
--------------------------------------------------------------------------
| page_size | | | | ...... | |
--------------------------------------------------------------------------
| useable |
-------------
| buffer |
-------------
| first |
-------------
|
|
| ---------
->| next |--
|--------| |
| count | |
|--------| |
| free | |
|--------| |
| memory | |
--------- |
|
-----------
|
| ---------
->| next | --> NULL
|--------|
| count |
|--------|
| free |
|--------|
| memory |
---------
实际上,内存池的全称应该是小内存块内存池,内存池管理的内存块的上限是
MEMORY_POOL_MAX_BYTES 个字节,超过这个数值的申请要求会被转调用至 MEMALLOC 函数,
正因为此,所以在释放内存的时候必须显示地指定要释放的内存块的大小,这样才能确保
正确地释放。
内存池是一个 memlist_t 指针类型的数组,数组的大小为 MEMORY_POOL_LISTS 数组内
的每个指针元素指向一条内存链,每个内存链由大小不同的内存页以单向链表的形式组成。
为了方便管理,凡是向内存池申请小于等于 MEMORY_POOL_MAX_BYTES 个字节的内存块都会
被自动调整为 MEMORY_POOL_ALIGN 的倍数,而每个内存链则管理着不同大小的内存块,可
用该公式计算:内存链管理的内存块大小 = MEMORY_POOL_MIN + 索引 * MEMORY_POOL_ALIGN。
由于每个内存链管理的内存块都很小,为了减少向系统申请内存的次数和内存碎片,内
存链的每个节点并不是内存块,而是一种比内存块大的多的被称为内存页的数据结构。内存
页由四部分组成:
1、next 是指向下一个内存页的指针;
2、count 是内存页中可供分配的内存块数;
3、free 是指向可分配内存块链表的指针;
4、内存页中用以分配内存块的缓存区,缓存大小由内存链的 page_size 成员决定。
**3.2**
分配功能的实现。
内存功能功能由函数 pool_alloc 实现,其声明如下:
void* pool_alloc( size_t bytes );
函数的参数是申请的动态内存字节数,返回值为一个指向动态内存首地址的空指针,
如果分配失败,返回的值为 NULL。
如前所述,内存池处理的内存块大小是有上限的,当申请的字节数大于阈值的时候,则
直接 MEMALLOC 函数,只有当申请的字节数小于等于阈值的时候才会使用内存池进行分配,
这个阈值就是前面提到的 MEMORY_POOL_MAX_BYTES。
为了便于分配,内存池不一定会刚好分配用户申请的那么大的内存,而是会对申请的
值进行一些调整。内存池不会分配小于 MEMORY_POOL_MIN_BYTES 的内存块,算法会将分配
的字节数调整至刚好不小于申请的字节数的 MEMORY_POOL_ALIGN 的倍数,默认管理的最小
内存块和对齐的字节数均为sizeof(void*),在32位的机器上其值为4。举几个例子:假设用
户申请的是14个字节,由于14不是4的倍数,算法自动将申请的字节数调整为刚好大于14的4
的倍数,也就是16。
在完成了字节对齐后,就可以确定由哪个内存链来进行分配,确定内存链后,先进入
该链的页面缓存,在页面缓存中寻找是否有可供使用的内存页。链的页面缓存是长度等于
MEMORY_POOL_BUFFER 的一个 mempage_t* 数组,对数组进行遍历,寻找第一个不等于 NULL
的值,这个值就是需要的内存页指针。需要注意的是,被分配的内存页是连在 mempage_t
下面的,所以在查找内存块时,需要对指针进行偏移操作,跳过 sizeof(mempage_t) 个字
节。
在找到了有空闲内存块的内存页后,将该内存页 free 指向的第一个空闲内存块取出,
作为返回的值,随后调整 free 指向第二个内存块。
但是缓存并不总是会有内存页的,当缓存为空时,就需要对链表进行遍历,以找到可用
的内存页。对链表的遍历并不是找到第一个可分配的内存页后就停止,这样做会让后面的分
配在不久之后又必须要遍历链表,来一趟不容易,不如来了以后多做点事,免得以后老是要
跑来爬楼梯。所以内存池的链表遍历实际上是一个整理操作,它会在遍历的过程中动态调整
链表的排列,把找到的可供分配的内存页逐一上调到链表的首位,这样遍历完成后,所有的
可分配内存页都被上调至了链表的前部,在调整的同时用找到的未满内存页重新填充页面缓
存。这里需要注意的是遍历的页面数,其实并不需要将链表这个遍历,在遍历的同时,用一
个无符号整数 count 记录遍历过的未满内存页,当 count == useable 的时候,表示后面已
经没有可供分配的内存页了,此时就可退出循环。
以上的行为均在 useable 大于 0 的时候发生,而当 useable 等于 0 的时候,既不需
要遍历页面缓存,也不需要遍历链表,直接调用底层动态内存分配函数 MEMALLOC 分配一个
内存页,将分配的内存页挂在内存链的首位,同时放入页面缓存,再将可用内存分割成一个
个小内存块并串联起来,最后返回串联起来的内存块链表中的第一个内存块即可。需要注意
的一点是内存页内可供分配的字节数是由链表的 page_size 成员来指定的。page_size 初
始时等于 0,当进行内存页申请的时候,如果 page_size = 0,则计算该内存链下每个内存
页需占用的字节数。为了确保不浪费内存,page_size 的计算结果必须要能被当前内存链管
理的内存块大小整除,如果 DEFAULT_PAGE_SIZE % BLOCK_SIZE(index) = 0,则令 page_size
等于 DEFAULT_PAGE_SIZE,否则令 page_size等于 BLOCK_SIZE(index) 的整数倍。
可用如下代码测试内存池内所有内存链的属性:
for( i = 1; i <= 256; ++i )
pool_alloc(i);
pool_print();
**3.3**
回收功能的实现。
回收实际上分为两个操作,一个是回收,一个是释放。
回收操作由函数 pool_dealloc 实现,其声明如下:
void pool_dealloc( void* ptr, size_t bytes );
函数的第一个参数是指向要回收的内存块的指针,第二个参数是要回收的内存块的大小。
进入函数后,先判断指针是否为空,接着判断内存块的大小,如果大于内存池所能管理
的最大内存块,则直接调用 MEMFREE 将之释放;如果小于等于则有可能是由内存池分配出去
的。注意,只是有可能而已!
根据内存块的大小,先计算出内存块理论上所属的内存链,然后对该内存链进行遍历,
在遍历内存页的过程中,判断内存块的地址是否落在内存页的首地址和末地址之间,如果是
的话,则表示找到了内存块所属的内存页,如果没有,则继续遍历,如果遍历至链尾依然没
有找到,则表示该内存块不是由内存池分配的,直接调用 MEMFREE 将之释放。
在确定了内存块所属的内存页后,依然不能马上执行回收操作,还必须确定内存块的地
址是否正确。由于分配时,是以 BLOCK_SIZE(index) 来进行对齐的,所以回收时的地址也必
须能满足这个对齐要求,亦即满足条件:(块地址 - 页首地址) % BLOCK_SIZE(index) == 0,
验证无误后,方能执行回收操作。
回收的操作很简单,只需要将内存块回收至空闲块链表即可。随后将内存页调整至链首,
这样做一来可以方便下次回收,二来可以方便当页面缓存使用完后,对链表的快速遍历。
回收操作只是把回收的内存块重新放进内存池,并不会把空闲的内存页释放给系统。假
如应用程序向内存池申请了大量的内存而后由内存池回收,此时内存池的使用率接近 0%,而
其他的应用程序却因为大量内存被内存池占用而无法正常运行,此时需要将内存池占用的大
量空闲内存释放给系统以供其他应用程序使用。很显然,回收操作并不能胜任,这个工作必
须由释放操作来完成。
释放操作由函数 pool_free 实现,其声明如下:
void pool_free( void* ptr, size_t bytes );
函数的第一个参数是指向要回收的内存块的指针,第二个参数是要回收的内存块的大小。
释放操作分为两步,第一步直接调用 pool_dealloc 完成内存块的回收,第二步遍历整
个链表,在遍历的过程中将空闲的内存页释放给系统。注意,第一个参数是可以为 NULL 的,
此时,将跳过第一步,直接执行第二步。
为什么在释放操作里是对整个链表的遍历呢?因为我预期大部分的时候执行的都会是归
还操作,只有当系统内存紧张的时候才需要执行释放操作,此时释放操作执行一次即可满足
要求,接下来就又可以使用回收操作了。譬如,可以调用 get_pool_dealloc_count 函数了
解执行了回收的次数,当达到某个阈值的时候就可以调用 pool_free 释放一些空闲的内存页。
**3.4**
并发控制。
在多线程环境下,内存池的操作将会因线程之间的切换而崩溃!为此,内存池在实现的
时候提供了并行加锁和解锁操作。为了移植,加锁和解锁的实现交由用户来实现,内存池只
负责调度。
设置加锁函数:void set_pool_lock( void (*lock)(size_t index) )。
设置解锁函数:void set_pool_unlock( void (*unlock)(size_t index) )。
两个函数的参数都是一个声明原型如 void f(size_t) 的函数指针。这里解释一下传递
进来的加锁和解锁函数为什么需要一个 size_t index 参数。这个 index 参数就是内存链的
索引值,仔细观察一下就会发现,实际上每个内存链是相互独立的,内存池最多可以允许
MEMORY_POOL_LISTS 个在不同的链表内的线程同时操作。通过 index 这个参数,实现加锁和
解锁功能的用户就可以对不同的链表分别进行加锁和解锁。例如在 Windows 系统下,可以先
调用 get_pool_lists_count 函数以获取内存链的总数,然后创建一个互斥量数组,数组的大
小就等于内存链的总数,在实现的加锁和解锁函数里可以根据 index 参数决定是对哪个互斥
量进行操作。
如果用户没有设置这两个并发加锁、解锁函数,但是在 yc_configuration.h 中定义了
__MACRO_C_YOUNG_LIBRARY_OPERATING_SYSTEM_SUPPORT_POSIX_THREAD__ 宏,则程序库将自
动启用 POSIX 多线程互斥控制。如果以上两者用户均设置了,则程序库会优先调用用户传递
进来的并发控制函数。
**3.5**
模板化。
在 C++ 中使用内存池可以借助模板来进行一下包装。STL 中的内存分配器已经为我们
提供了一个样本。
模板化的内存池源码在 young/youngcpp/ycpp_memory.hpp 中。下面只列出三个主要的
成员函数。
pointer allocate( size_type n )
{
return (pointer)( youngc::pool_alloc( sizeof(T) * n ) );
}
void deallocate( pointer ptr, size_type n )
{
youngc::pool_dealloc( ptr, sizeof(T) * n );
}
~pool_allocator()
{
youngc::pool_free( NULL, sizeof(T) );
}
**3.6**
移植。
内存池的实现大部分都是不依赖于任何系统的标准 C 代码,需要修改的只是调用动态
内存分配和释放的两个系统函数,为了便于移植,将它们定义为了两个宏,在一些嵌入式系
统中可能需要重新定义:
#define MEMALLOC( bytes) malloc( bytes )
#define MEMFREE( ptr ) free( ptr )
另外一个可能会带来移植问题的函数是 pool_print,有些嵌入式系统的 C 编译器不支
持标准输入和输出,如果移植到这些平台的时候需要把这个函数注释掉。
若要修改内存池的属性,可修改 3.1 中介绍的几个属性枚举值。
在不需要并发编程对函数指针支持的又不好的硬件平台上,把 pool_lock 和 pool_unlock
函数指针以及 set_pool_lock 和 set_pool_unlock 函数注释掉即可。