malloc算法对于系统运行的效率非常重要,DL malloc是很有效率的一个算法
越使用底层的功能接口,可能程序的移植性越差
内存分配工具
分配 | 释放 | 种类 | 可否重载 |
---|---|---|---|
malloc() | free() | C函数 | 不可 |
new | delete | C++表达式 | 不可 |
::operator new() | ::operator delete() | C++函数 | 可 |
allocator ::allocate() | allocator::deallocate() | C++标准库 | 可以自由设计并搭配容器 |
使用范例
// 以下均为申请512个字节的内存空间
void *p1 = malloc(512);
free(p1);
complex* p2 = new complex;
delete p2;
// 以下实质调用的是malloc和free
void *p3 = ::operator new(512);
::operator delete(p3);
// allocator有接口的标准,但是不同的编译器实现的标准可能存在差异
#ifdef _MSC_VER
//以下函数都是non-static,要通过object调用,以下分配3个ints.
int* p4 = allocator().allocate(3,(int*)0);// 第二参数默认值
allocator().deallocate(p4,3);
#endif
#ifdef _BORLANDC_
//以下函数都是non-static,要通过object调用,分配5个整数类型空间
int* p4 = allocator().allocate(5);// ()表示创建临时对象
allocator().deallocate(p4,5);
#endif
#ifdef _GNUC_//以下函数都是static
// 分配512个无指定类型的字节
void* p4 = alloc::allocate(512);
alloc::deallocate(p4,512);
//以下兩函數都是 non-static,定要通過 object 調用。以下分配 7 個 ints.
void* p4 = allocator().allocate(7);
allocator().deallocate((int*)p4,7);
//以下兩函數都是 non-static,定要通過 object 調用。以下分配 9 個 ints.
void* p5 = __gnu_cxx::__pool_alloc().allocate(9);
__gnu_cxx::__pool_alloc().deallocate((int*)p5,9);
#endif
new的本质(分配–转型–实例化)
Complex *pc = new Complex(1, 2);
// 用c语言进行其编译功能的解释
Complex *pc;
try{
void *mem = operator new(sizeof(Complex));//分配内存,实质调用malloc
pc = static_cast(mem);// 内存转型,赋值给指针
pc->Complex::Complex(1, 2);// 调用构造函数实例化内存(赋值)
}catch(std::bad_alloc){
// 若allocation失败就不执行constructor
}
operator new的本质
// 第二参数保证函数不抛出异常
void *operator new(size_t size, const std::nothrow_t &){
void *p;
while((p=malloc(size)) == 0){// 如果内存耗尽导致分配失败 (实质调用malloc)
_TRY_BEGIN
if(_callnewh(size) == 0)// 调用自定义函数进行处理
break;
_CATCH(std::bad_alloc)
return 0;
_CATCH_END
}
return p;
}
delete的本质(析构–释放)
delete pc;
// 使用c语言进行翻译
pc->~Complex();// 先析构
operator delete(pc);// 后释放
//operator delete源码
void __cdecl operator delete(void *p)_THROW0(){
free(p);
}
构造函数不能直接调用,析构函数可以
使用array new进行内存申请,必须使用array delete进行内存释放,如果不使用可能会导致内存泄漏
debugger模式下,malloc实际申请内存
placement new:运行将对象建构在已分配的内存中
#include
char *buf = new char[sizeof(Complex) * 3];
Complex *pc = new(buf)Complex(1, 2);// buf是已分配的空间
···
delete []buf;
// Complex *pc = new(buf)Complex(1, 2);的编译解释
Complex *pc;
try{
void *mem = operator new(sizeof(Complex), buf);//直接返回buf,无作用
pc = static_cast(mem);// 转型
pc->Complex::Complex(1, 2);// 调用构造函数赋值
}
没有placement delete,因为placement new没有分配内存
重载全局的 ::operator new/::operator delete
// 更改全局的内存管理函数影响很大,尽量不用进行更改
void* myAlloc(size_t size){
return malloc(size);
}
void myFree(void *ptr){
return free(ptr);
}
inline void *operator new(size_t size){
cout<< "global new()";
return myAlloc(size);
}
inline void *operator new[](size_t size){
cout << "global new[]()";
return myAlloc(size);
}
inline void operator delete(void *ptr){
cout<< "global delete()";
myFree(ptr);
}
inline void operator delete[](void *ptr){
cout<< "global delete[]()";
myFree(ptr);
}
重载类中的operator new / operator delete
Foo *p = new Foo;编译后的动作
···
delete p;
// Foo *p = new Foo;编译后的动作
try{
void *mem = operator new(sizeof(Foo));// 申请内存
p = static_cast(mem);// 指针类型转换
p->Foo::Foo(1, 2);// 构造函数赋值
}
// delete p;编译后的动作
p->~Foo();
operator delete(p);
// 类内重载
class Foo{
public:
void *operator new(size_t);
void operator delete(void*, size_t);// 第二参数可以不写
void *operator new(size_t size, void *start){
return start;
}
void *operator new(size_t, long extra){
return malloc(size+extra);
}
void *operator delete(void*, long){
cout<< "operator delete(void*, long)"<< endl;
}
}
内存分配好后如果失败,必须要将内存进行回收。如果自定义的opereator new执行出错,会调用相应的析构函数
内存管理的核心目标就是提高速度、降低空间使用
内存池是由固定大小数组组成,这些数组通过指针形成链表,可能分散在不同的内存区域
使用单链表模拟allocator
// 表示内存管理是一块池塘,使用链表进行管理
#include
#include
using namespace std;
class Screen{
public:
Screen(int x):i(x){};
int get(){return i;}
void * operator new(size_t);
void operator delete(void*, size_t);
//···
private:
Screen* next;
static Screen* freeStore;
static const int screenChunk;
private:
int i;
};
Screen* Screen::freeStore = 0;
const int Screen::screenChunk = 24;
void *Screen::operator new(size_t size){
Screen *p;
if(!freeStore){
// linked list是空的,所以申请一大块
size_t chunk = screenChunk * size;
FreeStore = p = reinterpret_cast(new char[chunk]);
// 将数组内元素进行串联
for(;p != &freeStore[screenChunk - 1]; ++p)
p->next = p + 1;
p->next = 0;
}
p = freeStore;
freeStore = freeStore->next;
return p
}
void Screen::operator delete(void *p, size_t){
// 将deleted object 插回free list前端
(static_cast(p))->next = freeStore;
freeStore = static_cast(p);
}
union同一个数据使用不同的形式进行展示
加入embedded pointer 节省next指针的开销,实现更高性能的内存池
/*
嵌入式指针工作原理:空闲内存块需要使用指针进行链接,一旦分配出去,这一部分即可被再利用,从而节省空间(32位系统4个字节,即内存块要大于4个字节)
*/
class Airplane {
private:
struct AirplaneRep {
unsigned long miles;
char type;
};
private:
union {
AirplaneRep rep;
Airplane* next;
};
public:
unsigned long getMiles() { return rep.miles; }
char getType() { return rep.type; }
void set(unsigned long m, char t)
{
rep.miles = m;
rep.type = t;
}
public:
// 这里加不加static都可以,编译器会给我们加上,因为类对象未构造,其行为未知。
static void* operator new(size_t size);
static void operator delete(void* deadObj, size_t size);
private:
static const int BLOCK_SIZE;
static Airplane* headOfFreeList;
};
Airplane* Airplane::headOfFreeList; // 空闲链表头
const int Airplane::BLOCK_SIZE = 512; // 空闲链表为空时,一次申请的块数
void* Airplane::operator new(size_t size){
// 继承会导致size不等,这里不做过多考虑
if (size != sizeof(Airplane))
return ::operator new(size);
Airplane* p = headOfFreeList;
if (p)
headOfFreeList = p->next;
else {
Airplane* newBlock = static_cast
(::operator new(BLOCK_SIZE*sizeof(Airplane)));
// 将申请的内存用链表串起来
for (int i = 1; i < BLOCK_SIZE - 1; ++i)
newBlock[i].next = &newBlock[i + 1];
newBlock[BLOCK_SIZE - 1].next = 0;
p = newBlock;
headOfFreeList = &newBlock[1];
}
return p;
}
// 将收回的指针放入单向链表的头部
void Airplane::operator delete(void* deadObj, size_t size) {
if (deadObj == 0) return;
if (size != sizeof(Airplane)) {
::operator delete(deadObj);
return;
}
//回收该对象,指针的next指向空闲链表头, 然后调整空闲链表头部为deadObj
(static_cast(deadObj))->next = headOfFreeList;
headOfFreeList = static_cast(deadObj);
}
抽象为allocator
class myAllocator
{
private:
struct obj {
struct obj* next; //embedded pointer
};
public:
void* allocate(size_t);
void deallocate(void*, size_t);
//void check();
private:
obj* freeStore = nullptr;
const int CHUNK = 5; //小一點方便觀察
};
void* myAllocator::allocate(size_t size)
{
obj* p;
if (!freeStore) {
//linked list 是空的,所以攫取一大塊 memory
size_t chunk = CHUNK * size;
freeStore = p = (obj*)malloc(chunk);
//cout << "empty. malloc: " << chunk << " " << p << endl;
//將分配得來的一大塊當做 linked list 般小塊小塊串接起來
for (int i = 0; i < (CHUNK - 1); ++i) { //沒寫很漂亮, 不是重點無所謂.
p->next = (obj*)((char*)p + size);
p = p->next;
}
p->next = nullptr; //last
}
p = freeStore;
freeStore = freeStore->next;
//cout << "p= " << p << " freeStore= " << freeStore << endl;
return p;
}
void myAllocator::deallocate(void* p, size_t)
{
//將 deleted object 收回插入 free list 前端
((obj*)p)->next = freeStore;
freeStore = (obj*)p;
}
类内对象或函数专门为该类服务,则使用static修饰。静态对象要在类外进行定义。
当operator new没能力为你分配申请的内存,会抛出一个std::bad_alloc exception。抛出异常之前会调用一个可以由用户指定的handler
// 分配失败的补救措施
typedef void*(new_handler)();
new_handler set_new_handler(new_handler p)throw();
两个新关键字
class Foo{
public:
Foo() =default;
Foo(const Foo&) =delete;
Foo& operator=(const Foo&) =delete;
~Foo() =default;
···
};
VC6标准库,其std::allocator的实现,只是以::operator new和::operator delete完成alloccate()和deallocate(),没有其他设计
#ifndef _FARQ
#define _FARQ
#define _PDFT ptrdiff_t
#define _SIZE size_t
#endif
template
class allocator{
public:
typedef _SIZE size_type;
typedef _PDFT difference_type;
typedef _Ty _FARQ *pointer;
typedef _Ty value_type;
pointer allocate(size_type _N, const void *){
return (_Allocate((difference_type)_N, (pointer)0));
}
void deallocate(void _FARQ *_P, size_type){
operator delete(_P);
}
};
// _Allocate的定义
template inline
_Ty _FARQ *_Allocate(_PDFT _N, _Ty _FARQ*){
if(_N < 0)
_N = 0;
return ((_Ty _FARQ*)operator new((_SIZE)_N *sizeof(_Ty)));
}
GCC的内存管理全部掌握在分配器手中,Loki可以将回收的内存归还操作系统
内存管理核心数据结构——三个指针=内存数据块首部 + 未分配数据块首部 + 内存数据块尾部
单向链表头插法可用于最高优先权的操作
&*i
,其中 i 是迭代器,表示取其指向元素的首地址
VicinityFind方法
for(;;){
if(向上未到尽头)
顺序查找;
if(向下未到尽头)
顺向查找;
}
Loki allocator特点
基本内存分配:当容器需要内存就调用operator new进行分配,当容器释放内存就调用operator delete。相比内存池,分配内存速度慢但是可以有效利用系统资源
不直接调用malloc,而使用operator new的原因是:虽然malloc速度快,但是不可以重载,失去了灵活性
大块内存的使用可以避免小内存块的cookie,增加内存空间的利用率
分配器追求的都是充分利用系统资源,eg:减少使用cookie
vector的本质是三个指针
17minp54