C/C++中内存分配与STL中的空间配置

最近在看侯捷的《STL源码解析》,按照里面的思路写了一个迷你的STL,由于STL中的内存分配和空间配置较为复杂,在这里总结一下。

C/C++中内存分配与STL中的空间配置

C语言中的内存分配

C语言中主要有三个函数进行内存分配,分别是malloc,realloc,calloc,释放内存的函数是free,头文件是<cstdlib>

1.malloc
函数原型:void *malloc(unsigned int num_bytes);。分配的是以字节计算的结果,返回的指针类型是void*
注意:不会进行初始化操作,需要手动计算分配的空间大小,同时需要对返回的类型进行强制转化。
使用:

int * a = (int*)malloc(sizeof(int) * 10);
for (int i = 0; i < 10; ++i)
    *(a + i) = i;
for (int i = 0; i < 10; ++i)
    cout << *(a + i) << " ";
// 0 1 2 3 4 5 6 7 8 9

2.realloc
对原来的地址进行重新分配。如果分配的空间小于原来的空间,还是会在原来的地址上。如果分配的空间大于原来的空间,如果原地址后面空间足够,则继续分配;否则会先释放原来的空间,然后分配新的地址。
函数原型:void *realloc(void *mem_address, unsigned int newsize);
使用:

int *b = (int*)realloc(a, sizeof(int) * 5);
cout << (int)b << " " << (int)a << endl;    // 地址一定相同
int *b = (int*)realloc(a, sizeof(int) * 50);
cout << (int)b << " " << (int)a << endl;    // 地址可能相同

3.calloc
函数原型:void *calloc(size_t n, size_t size);
两个参数分别是分配的元素个数和单个元素的大小。calloc的最大特点是会将分配的内存初始化为0
使用:

int *c =(int*)calloc(10, sizeof(int));
for (int i = 0; i < 10; ++i)
    cout << *(c + i) << " ";
// 0 0 0 0 0 0 0 0 0 0

4.free
函数原型:void free(void *ptr)
释放空间,但需要手动把指针置空,因为只是释放并没有改变指针的指向,只是这一块内存已经不属于这个指针。

C++中的内存分配

new/delete

C++ 中使用new/delete来代替C语言中的相关库函数。主要区别如下:
1. new/delete是操作符不是库函数
2. new会自动计算空间大小
3. new除了分配空间,还会自动调用对象的构造函数,对申请的空间进行构造
4. delete会先调用析构函数,然后释放空间

new/delete的多种使用

正常情况下new/delete都是完成空间申请,对象构造,对象析构,空间释放功能,但也有几个不同的函数原型,提供不同的功能。
1.plain new
这个就是正常的new,在分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void *) throw();

2.nothrow new
这是不抛出异常的new形式,如果分配失败,返回NULL
void * operator new(std::size_t,const std::nothrow_t&) throw();
void operator delete(void*) throw();

3.placement new
在一块已经分配空间的内存上构造对象,不分配新的空间,不会抛出异常。
void* operator new(size_t,void*);
void operator delete(void*,void*)

memory头文件中

在C++中,也提供了分配和构造对象的函数,位于memory头文件中。主要有以下相关的函数:

  1. allocate分配空间
  2. construct构造
  3. destroy析构
  4. deallocate释放内存
    memory的函数将new/delete的功能拆成四个部分,分别是申请空间,构建,析构,释放。
    具体使用:
allocator<int> alloc;
// 分配
auto begin = alloc.allocate(10);
auto p = begin;
// 构造
for (int i = 0; i < 10; ++i)
    alloc.construct(p++, i);
for (int i = 0; i < 10; ++i)
    printf("%d ", *(begin + i));
printf("\n");
// 析构
p = begin;
for (int i = 0; i < 10; ++i)
    alloc.destroy(p++);
// 释放内存
alloc.deallocate(begin, 10);

STL空间配置器

在STL的设计中,使用的是多层的空间配置器,因为频繁的申请释放空间将会造成内存的碎片化。在这里使用两层的空间配置器。

一级空间配置器

  1. 一级空间配置器分配的是大于128字节的空间
  2. 如果分配不成功,调用句柄释放一部分内存
  3. 如果还不能分配成功,抛出异常

二级空间配置器

维护一个链表,链表每个节点是8的倍数大小,每一个子链表拥有相同大小的节点。

这里写图片描述
图片来自网络,侵权删除。

  1. 当用户申请的空间小于128字节时,将字节数扩展到8的倍数,然后在自由链表中查找对应大小的子链表
  2. 如果在自由链表查找不到或者块数不够,则向内存池进行申请,一般一次申请20块
  3. 如果内存池空间足够,则取出内存
  4. 如果不够分配20块,则分配最多的块数给自由链表,并且更新每次申请的块数
  5. 如果一块都无法提供,则把剩余的内存挂到自由链表,然后向系统heap申请空间,如果申请失败,则看看自由链表还有没有可用的块,如果也没有,则最后调用一级空间配置器

SimpleAllocate

在STL的实现中,封装原始的alloc,原始的alloc只分配固定原始的空间,并不会构建和析构对象。封装后的SimpleAllocate能够分配空间,构建对象,析构对象,释放空间。构建对象使用palcement new,析构对象显式调用析构函数。
具体实现:

template<class T>
class SimpleAllocate
{
public:
    typedef T value_type;
    typedef size_t size_type;
    typedef value_type * value_ptr;
    static TinySTL::alloc alloc;        // 底层空间分配
    static value_ptr allocate(size_type n)
    {
        return static_cast<value_ptr>(alloc::allocate(n * sizeof(value_type)));
    }
    static void deallocate(value_ptr p, size_type n)
    {
        alloc::deallocate(p, n * sizeof(value_type));
    }
    static void construct(value_ptr p, const value_type &value)
    {
        new (p)value_type(value);       // placement new
    }
    static void destroy(value_ptr p)
    {
        p->~T();   // 显式调用析构函数
    }
};

template<class T>
allocator<T> SimpleAllocate<T>::alloc = allocator<T>();

目前的STL实现空间配置,setvectorstack等容器
完整代码见github

如有错误,欢迎指正~

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值