电商网站的商品主图,会建网站的人,wordpress能建论坛吗,做网站必须花钱吗目录示例版本1#xff1a;per-class allocator,1示例版本2#xff1a;per-class allocator,2最终版本#xff1a;static allocator针对版本三进行macro如果我们不针对对象做内存管理#xff0c;那么我们每次进行Foo* p new Foo(x);时总是会调用malloc函数。 尽管malloc函数… 目录示例版本1per-class allocator,1示例版本2per-class allocator,2最终版本static allocator针对版本三进行macro 如果我们不针对对象做内存管理那么我们每次进行Foo* p new Foo(x);时总是会调用malloc函数。 尽管malloc函数很快但是我们仍然有自己管理内存的必要。 在https://blog.csdn.net/qq_42604176/article/details/111234556中曾经记录到有些操作系统多次调用malloc函数会把原来很大的一块连续内存区域逐渐地分割成许多非常小而且彼此不相邻的内存区域也就是内存碎片。这也是一种隐患。 我们可以首先使用malloc函数申请一大段内存然后切割成若干个小块每次创建一个对象的时候就给一小块的内存。这样效率更高并且更容易管理。 并且如果没有经过特殊设计一次创建对象就调用一次malloc一次malloc就会得到两个cookie(8个字节)。创建多个对象的时候这样就会比较浪费空间。 内存池设计的目的就是要提高速度和降低浪费。
示例版本1per-class allocator,1
下面是示例
#includecstddef
#includeiostream
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;出于对内存管理的考虑我们需要挖一大块内存并且内存里面的小块需要用指针联系到一起。所以可以看到上面class内部会有一个next指针指向Screen类型的对象。但是这样会有一个遗憾因为多出来一个指针导致空间又浪费掉了。 接下来是函数的重载。核心的操作步骤就是单向链表的操作。将空闲的内存做成链表每次开辟新内存的话就将链表中的一个分配出去。
void* Screen::operator new(size_t size)
{Screen *p;if(!freeStore) {//linked list 是空的所以申请了一大块内存size_t chunk screenChunk * size; //一次挖24个对象的内存//将指针转型freeStore p reinterpret_castScreen*(new char[chunk]);//将一大块分割成小块当作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,size_t)
{//将delete object 插回free list前端(static_castScreen*(p))-next freeStore;freeStore static_castScreen*(p);
}测试函数 cout sizeof(Screen) endl; //16size_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];
效果 可以看出每个类之间的内存间隔是16确实没有cookie的内存。 16 00000280B9B21CF0 00000280B9B21D00 00000280B9B21D10 00000280B9B21D20 00000280B9B21D30 00000280B9B21D40 00000280B9B21D50 00000280B9B21D60 00000280B9B21D70 00000280B9B21D80 如果我们不对两个函数进行重载得到的结果如下由于电脑操作系统是多进程的所以可能在内存分配的时候有其他任务执行导致内存不是连续的估摸着间隔应该是5*16。 16 000001BA32211920 000001BA322120A0 000001BA32211A10 000001BA32211970 000001BA32211C90 000001BA32212460 000001BA322124B0 000001BA32212500 000001BA32211EC0 000001BA322119C0 在侯捷老师的PPT上是VC6、GNU环境下的结果如下
示例版本2per-class allocator,2
这个版本主要是利用union对第一版本的指针进行优化。 关于union这里做一个简单回顾毕竟基本没用过这东西。 具体细节可以参考https://blog.csdn.net/yuyanggo/article/details/49819667?utm_mediumdistribute.pc_relevant.none-task-blog-baidujs_baidulandingword-2spm1001.2101.3001.4242 同一个内存段可以用来存放几种不同类型的成员但在每一个时刻只能存在其中一种而不是同时存放几种。也就是说每一瞬间只有一个成员起作用其它的成员不起作用即不是同时都存在和起作用。 共用体变量中起作用的成员是最后一个存放的成员在存入一个新的成员后原有的成员就失去作用。 #include iostream
using namespace std;union test
{char mark;long num;float score;
}a;int main()
{// coutaendl; // wronga.mark b;couta.markendl; // 输出bcouta.numendl; // 98 字符b的ACSII值couta.scoreendl; // 输出错误值a.num 10;couta.markendl; // 输出换行 非常感谢suxin同学的指正couta.numendl; // 输出10couta.scoreendl; // 输出错误值a.score 10.0;couta.markendl; // 输出空couta.numendl; // 输出错误值couta.scoreendl; // 输出10return 0;
}由于union中的所有成员起始地址都是一样的所以a.mark、a.num和a.score的值都是一样的。 由于union里面的东西共享内存所以不能定义静态、引用类型的变量。 在union里也不允许存放带有构造函数、析构函数和复制构造函数等的类的对象但是可以存放对应的类对象指针。编译器无法保证类的构造函数和析构函数得到正确的调用由此就可能出现内存泄漏。 接下来看第二个版本
class Airplane {
private:struct AirplaneRep {unsigned long miles;char type;};
private:union {AirplaneRep rep; //此指针指向使用中的对象Airplane* next; //此指针指向free list上的对象};
//这些方法不是重点
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;}
//重载new和delete
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;
};
Airplane* Airplane::headOfFreeList;
const int Airplane::BLOCK_SIZE 512;
new的重载和第一个版本相似
void* Airplane::operator new(size_t size)
{//在继承发生时可能size大小有误if(size ! sizeof(Airplane))return ::operator new(size);Airplane* p headOfFreeList;if(p) //如果p有效就把链表头部向下移headOfFreeList p-next;else{//如果链表已空申请一大块内存Airplane* newBlock static_castAirplane*(::operator new(BLOCK_SIZE * sizeof(Airplane)));//将小块穿成一个freelistfor(int i 1; i BLOCK_SIZE - 1; i)newBlock[i].next newBlock[i1];newBlock[BLOCK_SIZE - 1].next 0;p newBlock;headOfFreeList newBlock[1];}return p;
}delete版本几乎与第一个版本一样都存在着没有将内存归还给操作系统的问题这个并不属于内存泄漏因为内存仍然掌握在我们手中。
//operator delete截获一个内存块
//如果大小正确就把它加到freelist前端
void Airplane::operator delete(void* deadObject, size_t size)
{if(deadObject 0) return;if(size ! sizeof(Airplane)) {::operator delete(deadObject);return;}Airplane* carcass static_castAirplane*(deadObject);carcass-next headOfFreeList;headOfFreeList carcass;
}最终版本static allocator
刚刚我们都是针对一个class单独重载它的new和delete这样会导致代码的重复性。我们将刚刚的动作抽象出来放到一个class里面。 每个allocator object 都是分配器它体内维护一个freelist不同的allocator objects维护者不同的freelists。
class allocator
{
private:struct obj {struct obj* next;};
public:void* allocate(size_t);void deallocate(void*,size_t);
private:obj* freeStore nullptr;const int CHUNK 5; //这里小一些以便观察
};void allocator::deallocate(void* p,size_t)
{//将*p收回插入free list 前端((obj*)p)-next freeStore;freeStore (obj*)p;
}void* allocator::allocate(size_t size)
{obj* p;if(!freeStore) {//linked list为空于是申请一大块size_t chunk CHUNK * size;freeStore p (obj*)malloc(chunk);//将分配得来的一大块当作linked list//串接起来for(int i 0; i (CHUNK - 1); i) {p-next (obj*)((char*)p size);p p-next;}p-next nullptr; }p freeStore;freeStore freeStore-next;return p;
}下面是是类调用分配器 每个allocator object 都是分配器它体内维护一个freelist不同的allocator objects维护不同的freelists 所有与内存相关的细节由allocator接管。我们的工作是让application classes正确运作。
class Foo {
public:long L;string str;static allocator myAlloc;
public:Foo(long l):L(1){}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;如上所示具体的内存分配的细节都不再由应用类所知晓。 测试代码如下注意这里不要使用using namespace std;因为标准库里也有allocator。
#includecstddef
#includeiostream//using namespace std;
namespace static_allocator
{class allocator{private:struct obj {struct obj* next;};public:void* allocate(size_t);void deallocate(void*, size_t);private:obj* freeStore nullptr;const int CHUNK 5; //这里小一些以便观察};void allocator::deallocate(void* p, size_t){//将*p收回插入free list 前端((obj*)p)-next freeStore;freeStore (obj*)p;}void* allocator::allocate(size_t size){obj* p;if (!freeStore) {//linked list为空于是申请一大块size_t chunk CHUNK * size;freeStore p (obj*)malloc(chunk);//将分配得来的一大块当作linked list//串接起来for (int i 0; i (CHUNK - 1); i) {p-next (obj*)((char*)p size);p p-next;}p-next nullptr;}p freeStore;freeStore freeStore-next;return p;}class Foo {public:long L;std::string str;static allocator myAlloc;public:Foo(long l) :L(1) {}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;void test_static_allocator_3(){std::cout \n\n\ntest_static_allocator().......... \n;{Foo* p[100];std::cout sizeof(Foo) sizeof(Foo) std::endl;for (int i 0; i 23; i) { //23,任意數, 隨意看看結果 p[i] new Foo(i);std::cout p[i] p[i]-L std::endl;}//Foo::myAlloc.check();for (int i 0; i 23; i) {delete p[i];}//Foo::myAlloc.check();}}
}int main()
{static_allocator::test_static_allocator_3();return 0;
}打印效果如下 可以看到allocator每一次调用malloc都是一次性取5个元素。所以每5个元素一定的是相邻的。可以从效果图的倒数第三个与倒数第四个之间看出。 重点关注这个写法。
针对版本三进行macro
具体步骤如下将黄色部分替换成蓝色部分即可。 每次使用的话直接在类内部调用两个宏就行了。 需要注意一个知识点 #define后面的是续行符表示下面一行是紧接着当前行的一般用于将十分长的代码语句分几zhuan段写语句本身要shu求必须是一行。 要注意\后面除了换行回车不能有任何字符空格也不行
#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: complexdouble c;string str;
public:Goo(const complexdouble x) : c(x) { }
};
//in class implementation file
IMPLEMENT_POOL_ALLOC(Goo)