众所周知,STL中有六大组件:
- 容器
- 容器适配器
- 仿函数
- 迭代器
- 算法
- 空间配置器
在之前的学习中前面五种我们都已经接触过了,但是在学习其他组件的过程中,我们会发现一个东西隐藏在了一切组件的背后,它就是空间配置器
以STL的实现角度而言,空间配置器是第一个就需要介绍的组件。因为整个STL的操作对象都存放在容器之内,而容器一定需要配置空间来存放数据的。
在我们平时写程序时,需要开辟空间可以使用malloc或者new,那么为什么还要空间配置器呢?今天先来聊一聊一级空间配置器。
我们知道的malloc和new的区别之一是malloc开辟内存失败后会返回0,而new开辟内存失败后会抛异常。而一级空间配置器就是更大的程度来合理运用空间。它的内部设计实际就是为了压榨剩余的内存,达到内存的高效运用。所以一级空间配置器内部其实就是malloc和free的封装,然后尽量的开辟出你想要的内存空间,就算系统内部的剩余内存空间小于你所申请的内存空间,它都会努力尝试开辟出来。
接下来一起看看一级空间配置器主要的源码:(我在这里只截取了函数的声明,具体定义在讲解的时候在附上代码)
#ifndef __THROW_BAD_ALLOC
# include <stdio.h>
# include <stdlib.h>
# define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
# else
# include <new>
# define __THROW_BAD_ALLOC throw std::bad_alloc()
# endif
#endif
//一级空间配置器定义开始,注意没有“template型参数”,而inst完全没有排上用场
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);
//释放空间
static void deallocate(void* __p, size_t /* __n */);
//重配置空间
static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz);
//仿真C++的set_new_handler()函数
static void (* __set_malloc_handler(void (*__f)()))();
};
一级空间配置器中重要的函数就是allocate、deallocate、reallocate了
allocate()及其相关函数的实现
#define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
static void* allocate(size_t __n)
{
void* __result = malloc(__n);//内部直接调用malloc()分配内存
//当申请内存失败时,改调用_S_oom_malloc()
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}
//内存不足的处理函数,初值为0,等待用户自定义
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif
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;
//由于将内存不足处理函数初值设定为0,若用户没有自定义函数去处理,那么还是抛出异常
if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
//用户自定义了内存不足处理函数则调用,企图释放内存
(*__my_malloc_handler)();
//继续调用malloc()尝试配置空间
__result = malloc(__n);
if (__result) return(__result);
}
}
我们看到程序中的for循环好像是一个死循环,其实不然。来分析一下,_malloc_alloc_oom_handler初始值为0,如果用户有自定义内存不足处理函数时,那么下面的_my_malloc_handler就不会为0。反之,如果没有自定义的函数,那么就会直接抛出异常. 所以这个循环可以在两个条件下退出:
- 在反复使用用户定义了的释放内存函数后,我们成功的分配了指定大小的内存,返回指向该内存区域的首地址
- 用户没有定义相应的处理内存不足函数,直接抛出异常
但是设想,如果用户定义的方法已经不能开辟出空间了,那么这个程序还是一个死循环。所以最好在用户自己定义的处理函数中解决这种情况,可以判断一下有没有释放出空间,有的话就继续,没有的话就可以抛出异常。
reallocate函数
和allocate函数原理一样,这里不多做说明:
#define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);//内部直接调用realloc()函数
//申请内存失败,改调用_S_oom_realloc()函数
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}
//内存不足的处理函数,初值为0,等待用户自定义
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif
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_BAD_ALLOC; }
(*__my_malloc_handler)();
__result = realloc(__p, __n);
if (__result) return(__result);
}
}
释放内存的函数deallocate()
static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}
一级空间配置器是以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作,并实现了C++的set_new_handler()函数,因为它不能直接运用C++的set_new_handler机制。
//static void (* __set_malloc_handler(void (*__f)()))()
{
//保存旧的内存不足处理函数
void (* __old)() = __malloc_alloc_oom_handler;
//重新定义处理函数
__malloc_alloc_oom_handler = __f;
return(__old);
}
这个函数的名字和参数列表看起来比较复杂,来看看它的真面目:
一级空间配置器的主要内容就这么多了,上一张图再说明一下分配内存的过程:
了解了这么多,我们来自己模拟实现一下一级空间配置器,直接上代码:
模拟实现一级空间配置器
malloc_alloc.h
#include <iostream>
#include <stdlib.h>
using namespace std;
#include <Windows.h>
#define _THROW_BAN_ALLOC 0
//一级空间配置器
template<int inst>
class _MallocAllocTemplate
{
public:
static void* Allocate(size_t n)//开辟空间
{
void* result = malloc(n);//调用malloc开辟空间
if (result == 0)
{
result = _Oom_Malloc(n);//开辟失败就调用_Oom_Malloc
}
return result;
}
static void* Reallocate(void* p, size_t n)//开辟空间,在源空间的基础上
{
void* result = realloc(p, n);//调用realloc开辟空间
if (result == 0)
{
result = _Oom_Realloc(p, n);//开辟失败调用_Oom_Realloc
}
return result;
}
static void Deallocate(void* p)//释放空间
{
free(p);//内部调用free释放空间
}
//该函数接收一个返回值为空,参数为空的函数指针作为参数,最后返回一个返回值和参数均为空的函数指针
static void(*Set_Malloc_Handler(void(*f)()))()
{
void(*old)() = _Malloc_Alloc_Oom_Handler;//保存旧的处理例程
_Malloc_Alloc_Oom_Handler = f; //重新设置新的处理例程
return old;
}
private:
static void(* _Malloc_Alloc_Oom_Handler)();
static void* _Oom_Malloc(size_t n)
{
void(*_My_Malloc_Handler)();
void* result;
for (;;)
{
_My_Malloc_Handler = _Malloc_Alloc_Oom_Handler;//用户自定义处理函数
//因为内存不足处理函数初值设置的是0,如果用户没有自定义,就直接抛出异常
if (_My_Malloc_Handler == 0)
{
_THROW_BAN_ALLOC;
}
(*_My_Malloc_Handler)();//若定义了,则调用用户自定义处理函数
result = malloc(n);//再次开辟内存
if (result)
return result;
}
//不断的尝试释放和配置是因为用户不知道还需要释放多少内存来满足分配需求,只能逐步的释放配置
}
static void* _Oom_Realloc(void* p, size_t n)
{
void(*_My_Malloc_Handler)();
void* result;
for (;;)
{
_My_Malloc_Handler = _Malloc_Alloc_Oom_Handler;
if (_My_Malloc_Handler == 0)
{
_THROW_BAN_ALLOC;
}
(*_My_Malloc_Handler)();
result = realloc(p, n);
if (result)
return result;
}
}
};
//内存不足处理函数初值设置为0,等用户自己定义
template<int inst>
void(*_MallocAllocTemplate<inst>::_Malloc_Alloc_Oom_Handler)() = 0;
测试代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "MallocAlloc.h"
//直接将参数inst指定为0
typedef _MallocAllocTemplate<0> Malloc_Alloc;
//自定义内存不足处理函数
void DoFreeAlloc()
{
for (;;)
{
cout << "tyring to release space" << endl;
}
}
void test()
{
//测试函数Allocate()
int* p = (int*)Malloc_Alloc::Allocate(sizeof(int)* 5);
for (size_t i = 0; i < 5; ++i)
{
p[i] = i;
}
//测试函数Reallocate()
p = (int*)Malloc_Alloc::Reallocate(p, sizeof(int)* 10);
for (int i = 5; i < 10; ++i)
{
p[i] = i;
}
for (int i = 0; i < 10; ++i)
{
cout << p[i] << " ";
}
cout << endl;
Malloc_Alloc::Deallocate(p);
//自定义一个内存不足处理函数进行测试
Malloc_Alloc::Set_Malloc_Handler(DoFreeAlloc);
//开辟一块肯定会申请失败的空间
int* q = (int*)Malloc_Alloc::Allocate(sizeof(int*)* (102410241024));
Malloc_Alloc::Deallocate(q);
}
int main()
{
test();
system("pause");
return 0;
}