内存管理
内存申请调用
小插曲:new为什么不需要指明申请的大小
- 其new底层编译时会对其展开,其operator new(size_t ),其在编译时就确定了new 申请对应的类型,其大小就被默认写死到对应函数,所以相应重载该operator new(size_t ),第一个参数类型是不能修改的
malloc内存申请
malloc申请空间时,记录其空间大小(其空间上方,有一个数据头,头部信息就记录了申请空间的大小),当调用free函数时,即需要读取头部信息得到需要释放的字节数
申请单个元素
申请连续空间
-
比申请单个元素空间多了计数,该计数用于delete[] 时读取需要析构几次指向其他地方的内存,而若不使用delete [],则导致只析构一处内存,其他元素指向将发生内存泄露
-
array new可能发生的内存泄露
-
若array new没有相应的array delete,造成内存泄露,其不是直接申请块内存泄露,而是间接指向造成内存泄露
glibc堆分配算法
其采取多种算法组合
- 它对于小于64bit的空间申请总是类似于对象池的方法 (较少的内部碎片)
- 对于大于64bit而小于512bit的,它会根据情况采取上述方法的最佳折中策略
- 对于大于512字节的空间申请采用的是最佳适配算法
- 对于大于128KB的申请,它会使用mmap机制直接向操作系统申请空间。
对象池
一些场合,被分配对象的大小是较为固定的几个值,这时候我们可以针对这样的特征设计一个更为高效的堆算法,称为对象池
对象池思路很简单,如果每一次分配的空集大小都一样,那么就可以按照这个每次请求分配的大小作为一个单位,把整个堆空间划分为大量的小块,每次请求的时候只需要找到小块就可以了。
对象池每次仅需请求一个固定的大小,因此实现起来很容易。由于每次总是只请求一个单位的内存,因此请求得到满足的速度非常快,无需查找一个足够大的空间。
对每一个需要处理的类A,B,C都重写operator new,那我们是否可以将这同一个功能集中到一个地方去吗?全局的函数?一个类?
对象池实例1
#include <cstddef>
#include <iostream>
namespace sqh
{
//ref. C++Primer 3/e, p.765
//per-class allocator
class Screen {
public:
Screen(int x) : i(x) { };
int get() { return i; }
void* operator new(size_t);
void operator delete(void*, size_t); //(2)
//! void operator delete(void*); //(1) 二擇一. 若(1)(2)並存,會有很奇怪的報錯 (摸不著頭緒)
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 是空的,所以攫取一大塊 memory
//以下呼叫的是 global operator new
size_t chunk = screenChunk * size;
freeStore = p =
reinterpret_cast<Screen*>(new char[chunk]);//攫取一大塊 memory,new char[chunk]其没有构造语义,我们此时只想申请内存
//將分配得來的一大塊 memory 當做 linked list 般小塊小塊串接起來
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) //(1)
void Screen::operator delete(void *p, size_t) //(2)二擇一
{
//將 deleted object 收回插入 free list 前端
(static_cast<Screen*>(p))->next = freeStore;//头插
freeStore = static_cast<Screen*>(p);
}
//-------------
void test_per_class_allocator_1()
{
cout << "\ntest_per_class_allocator_1().......... \n";
cout << sizeof(Screen) << endl; //8
size_t const N = 100;
Screen* p[N];
for (int i=0; i< N; ++i)
p[i] = new Screen(i);
//輸出前 10 個 pointers, 用以比較其間隔
for (int i=0; i< 10; ++i)
cout << p[i] << endl;
for (int i=0; i< N; ++i)
delete p[i];
}
} //namespace
对象池实例2
#include <cstddef>
#include <iostream>
namespace sqh
{
//ref. Effective C++ 2e, item10
//per-class allocator
class Airplane { //支援 customized memory management
private:
struct AirplaneRep {
unsigned long miles;
char type;
};
private:
//内嵌结构有数据了吗,每个节点是一个联合体
union {
AirplaneRep rep; //此針對 used object
Airplane* next; //此針對 free list----》Embedded pointer 内嵌指针
};
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 void* operator new(size_t size);
static void operator delete(void* deadObject, size_t size);
private:
static const int BLOCK_SIZE;
static Airplane* headOfFreeList;//被所有该类对象所共享,当申请10W个对象,那么当释放时,将会构建10W个对象大小的链表
};
Airplane* Airplane::headOfFreeList;
const int Airplane::BLOCK_SIZE = 512;
void* Airplane::operator new(size_t size)
{
//如果大小錯誤,轉交給 ::operator new()
if (size != sizeof(Airplane))
return ::operator new(size);
Airplane* p = headOfFreeList;
//如果 p 有效,就把list頭部移往下一個元素
if (p)
headOfFreeList = p->next;
else {
//free list 已空。配置一塊夠大記憶體,
//令足夠容納 BLOCK_SIZE 個 Airplanes
Airplane* newBlock = static_cast<Airplane*>
(::operator new(BLOCK_SIZE * sizeof(Airplane)));
//組成一個新的 free list:將小區塊串在一起,但跳過
//#0 元素,因為要將它傳回給呼叫者。
for (int i = 1; i < BLOCK_SIZE-1; ++i)
newBlock[i].next = &newBlock[i+1];
newBlock[BLOCK_SIZE-1].next = 0; //以null結束
// 將 p 設至頭部,將 headOfFreeList 設至
// 下一個可被運用的小區塊。
p = newBlock;
headOfFreeList = &newBlock[1];
}
return p;
}
// operator delete 接獲一塊記憶體。
// 如果它的大小正確,就把它加到 free list 的前端
void Airplane::operator delete(void* deadObject,
size_t size)
{
if (deadObject == 0) return;
if (size != sizeof(Airplane)) {
::operator delete(deadObject);
return;
}
Airplane *carcass =
static_cast<Airplane*>(deadObject);
carcass->next = headOfFreeList;
headOfFreeList = carcass;
}
//-------------
void test_per_class_allocator_2()
{
cout << "\ntest_per_class_allocator_2().......... \n";
cout << sizeof(Airplane) << endl; //8
size_t const N = 100;
Airplane* p[N];
for (int i=0; i< N; ++i)
p[i] = new Airplane;
//隨機測試 object 正常否
p[1]->set(1000,'A');
p[5]->set(2000,'B');
p[9]->set(500000,'C');
cout << p[1] << ' ' << p[1]->getType() << ' ' << p[1]->getMiles() << endl;
cout << p[5] << ' ' << p[5]->getType() << ' ' << p[5]->getMiles() << endl;
cout << p[9] << ' ' << p[9]->getType() << ' ' << p[9]->getMiles() << endl;
//輸出前 10 個 pointers, 用以比較其間隔
for (int i=0; i< 10; ++i)
cout << p[i] << endl;
for (int i=0; i< N; ++i)
delete p[i];
}
} //namespace
对象池实例3 allocator
namespace sqh03
{
class allocator
{
private:
struct obj { //为什么这是内嵌指针?allocator是对象的一部分,allocator成员为obj*next nb的手法
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; //小一點方便觀察分配的5的区块空间连续
};
void* allocator::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 allocator::deallocate(void* p, size_t)
{
//將 deleted object 收回插入 free list 前端
((obj*)p)->next = freeStore;
freeStore = (obj*)p;
}
void allocator::check()
{
obj* p = freeStore;
int count = 0;
while (p) {
cout << p << endl;
p = p->next;
count++;
}
cout << count << endl;
}
//--------------
class Foo {
public:
long L;
string str;
static allocator myAlloc;
public:
Foo(long l) : L(l) { }
static void* operator new(size_t size)
{ return myAlloc.allocate(size); }
static void operator delete(void* pdead, size_t size)
{ return myAlloc.deallocate(pdead, size); }
};
allocator Foo::myAlloc;
class Goo {
public:
complex<double> c;
string str;
static allocator myAlloc;
public:
Goo(const complex<double>& x) : c(x) { }
static void* operator new(size_t size)
{ return myAlloc.allocate(size); }
static void operator delete(void* pdead, size_t size)
{ return myAlloc.deallocate(pdead, size); }
};
allocator Goo::myAlloc;
//-------------
void test_static_allocator_3()
{
cout << "\n\n\ntest_static_allocator().......... \n";
{
Foo* p[100];
cout << "sizeof(Foo)= " << sizeof(Foo) << endl;
for (int i=0; i<23; ++i) { //23,任意數, 隨意看看結果
p[i] = new Foo(i);
cout << p[i] << ' ' << p[i]->L << endl;
}
//Foo::myAlloc.check();
for (int i=0; i<23; ++i) {
delete p[i];
}
//Foo::myAlloc.check();
}
{
Goo* p[100];
cout << "sizeof(Goo)= " << sizeof(Goo) << endl;
for (int i=0; i<17; ++i) { //17,任意數, 隨意看看結果
p[i] = new Goo(complex<double>(i,i));
cout << p[i] << ' ' << p[i]->c << endl;
}
//Goo::myAlloc.check();
for (int i=0; i<17; ++i) {
delete p[i];
}
//Goo::myAlloc.check();
}
}
} //namespace
macro优化对象池
namespace jj10
{
using sqh03::allocator;
//借鏡 MFC macros.
// DECLARE_POOL_ALLOC -- used in class definition
#define DECLARE_POOL_ALLOC() \
public: \
void* operator new(size_t size) { return myAlloc.allocate(size); } \
void operator delete(void* p) { myAlloc.deallocate(p, 0); } \
protected: \
static allocator myAlloc;
// IMPLEMENT_POOL_ALLOC -- used in class implementation file
#define IMPLEMENT_POOL_ALLOC(class_name) \
allocator class_name::myAlloc;
// in class definition file
class Foo {
DECLARE_POOL_ALLOC()
public:
long L;
string str;
public:
Foo(long l) : L(l) { }
};
//in class implementation file
IMPLEMENT_POOL_ALLOC(Foo)
// in class definition file
class Goo {
DECLARE_POOL_ALLOC()
public:
complex<double> c;
string str;
public:
Goo(const complex<double>& x) : c(x) { }
};
//in class implementation file
IMPLEMENT_POOL_ALLOC(Goo)
void test_macros_for_static_allocator()
{
cout << "\n\n\ntest_macro_for_static_allocator().......... \n";
Foo* pF[100];
Goo* pG[100];
cout << "sizeof(Foo)= " << sizeof(Foo) << endl;
cout << "sizeof(Goo)= " << sizeof(Goo) << endl;
for (int i=0; i<23; ++i) { //23,任意數, 隨意看看結果
pF[i] = new Foo(i);
pG[i] = new Goo(complex<double>(i,i));
cout << pF[i] << ' ' << pF[i]->L << "\t";
cout << pG[i] << ' ' << pG[i]->c << "\n";
}
for (int i=0; i<23; ++i) {
delete pF[i];
delete pG[i];
}
}
} //namespace
内嵌指针
poll_alloc 对象池
- 其不带相应的头部信息,当其相应桶管理的内存不足时,才去调用malloc申请内存,以此来减少malloc申请导致的overhead
对于GUN4.9版,容器实现内存池分配器
bitmap_allocator
- 对于多次申请较小内存的类型元素,其overhead很大,其内存利用率很低,是对内存的极度浪费
placement new 定位new
- 可对一块空间进行重新调用构造函数
- 可用于对array new的重新赋值,同等于调用构造函数
Object *s=new Object(10);
new(s) Object(100);//定位new,以s所指向的空间(不开辟空间)调动构造函数构造对象
...
delete s