最近在看侯捷的《STL源码解析》,按照里面的思路写了一个迷你的STL,由于STL中的内存分配和空间配置较为复杂,在这里总结一下。
C语言中主要有三个函数进行内存分配,分别是
malloc
,realloc
,calloc
,释放内存的函数是free
,头文件是
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语言中的相关库函数。主要区别如下:
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*)
在C++中,也提供了分配和构造对象的函数,位于memory
头文件中。主要有以下相关的函数:
allocate
分配空间construct
构造destroy
析构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的设计中,使用的是多层的空间配置器,因为频繁的申请释放空间将会造成内存的碎片化。在这里使用两层的空间配置器。
维护一个链表,链表每个节点是8的倍数大小,每一个子链表拥有相同大小的节点。
在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(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 SimpleAllocate::alloc = allocator();
目前的STL
实现空间配置,set
,vector
,stack
等容器
完整代码见github
如有错误,欢迎指正~