针对一个class写出它的内存管理池以及总结出allocator类(三个版本)

目录

    • 示例版本1:per-class allocator,1
    • 示例版本2:per-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个字节)。创建多个对象的时候这样就会比较浪费空间。
内存池设计的目的就是要提高速度和降低浪费。

示例版本1:per-class allocator,1

下面是示例:

#include<cstddef>
#include<iostream>
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_cast<Screen*>(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_cast<Screen*>(p))->next = freeStore;freeStore = static_cast<Screen*>(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环境下的,结果如下:
在这里插入图片描述

示例版本2:per-class allocator,2

这个版本主要是利用union对第一版本的指针进行优化。
关于union这里做一个简单回顾,毕竟基本没用过这东西。
具体细节可以参考:https://blog.csdn.net/yuyanggo/article/details/49819667?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-2&spm=1001.2101.3001.4242

同一个内存段可以用来存放几种不同类型的成员,但在每一个时刻只能存在其中一种,而不是同时存放几种。也就是说,每一瞬间只有一个成员起作用,其它的成员不起作用,即不是同时都存在和起作用。
共用体变量中起作用的成员是最后一个存放的成员,在存入一个新的成员后,原有的成员就失去作用。

#include <iostream>
using namespace std;union test
{char mark;long num;float score;
}a;int main()
{// cout<<a<<endl; // wronga.mark = 'b';cout<<a.mark<<endl; // 输出'b'cout<<a.num<<endl; // 98 字符'b'的ACSII值cout<<a.score<<endl; // 输出错误值a.num = 10;cout<<a.mark<<endl; // 输出换行 非常感谢suxin同学的指正cout<<a.num<<endl; // 输出10cout<<a.score<<endl; // 输出错误值a.score = 10.0;cout<<a.mark<<endl; // 输出空cout<<a.num<<endl; // 输出错误值cout<<a.score<<endl; // 输出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_cast<Airplane*>(::operator new(BLOCK_SIZE * sizeof(Airplane)));//将小块穿成一个freelistfor(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;
}

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_cast<Airplane*>(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。

#include<cstddef>
#include<iostream>//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: complex<double> c;string str;
public:Goo(const complex<double>& x) : c(x) {  }   
};
//in class implementation file
IMPLEMENT_POOL_ALLOC(Goo) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu 切换用户的命令[shell, linux]

使用ubuntu过程中免不了和shell(终端)打交道, 也不可避免在各种用户之间进行切换, 从而实现对各帐户的管理, 这个就涉及到了一个比较基础又很重要的工作,怎么样切换用户, 对于LINUX老鸟来说,这个根本不值不提的东东却让新手挠头不已, 现在给出普通用户和超级用户切换的命令(附图…

关于new handler与default、delete关键字

在https://blog.csdn.net/qq_42604176/article/details/111638568的operate_new源代码长啥样中谈到过new handler。 当operator new不能够分配出申请的内存时&#xff0c;会抛出bad_alloc 异常。有的编译器会返回0. 当定义成new(nothrow) Foo&#xff1b;就不会抛异常&#xff…

模式匹配运算符–Shell

转载&#xff1a;http://www.firefoxbug.net/?p722 Var/home/firefox/MyProgram/fire.login.name ${Variable#pattern}:如果模式匹配于变量值的开头处&#xff0c;则删除匹配的最短部分&#xff0c;并且返回剩下的部分 例子&#xff1a; [fire]$ echo ${Var#*/} [fire]$ home/…

VC6、BC5、G2.9标准分配器一览

目录VC6标准分配器BC5标准分配器G2.9标准分配器VC6标准分配器 VCx中源码可以在电脑路径中找&#xff1a; [D:\Program Files\VisualStudio\Community\VC\Tools\MSVC\14.28.29333\include\xmemory] 不过太多了。大概在837行左右有关于allocator代码。还是先看侯捷PPT上的吧。 …

css clear属性_CSS中的clear属性

css clear属性CSS | 清除财产 (CSS | clear Property) We know so much about float property and how it is used for styling our web pages. If you do not remember the float property, lets help jog your memory. The float property is used to set the elements in a …

std::alloc具体细节

G2.9 std::alloc的缺点&#xff1a; 1、在alloc::deallocate中没有将拿到的内存资源还给操作系统&#xff0c;在多任务中将占用很大资源 2、alloc::deallocate函数没有检查传入的p指针的有效性。在这里它默认p为alloc::allocate取得。 如果p并非alloc::allocate取得&#xf…

修改函数的返回地址

这篇随笔源自今天看的这篇文章http://www.cnblogs.com/bluesea147/archive/2012/05/19/2508208.html 1. 如何修改函数返回地址 今天主要写测试程序思考和验证了一下这个问题&#xff0c;先看一下这个&#xff23;程序 1 #include <stdio.h>2 void foo(){3 int a,…

从源码角度剖析VC6下的内存分配与切割的运作

目录前言1、heap初始化2、第一次分配内存&#xff0c;计算真正区块大小3、new_region管理中心4、__sbh_alloc_new_group()切割第一次分配好的内存5、开始切割内存前言 malloc与free带来的内存管理是应付小区块的&#xff0c;即SBH(small block heap)&#xff0c;这点也可以从源…

最长公共子序列求序列模板提_最长公共子序列

最长公共子序列求序列模板提Description: 描述&#xff1a; This question has been featured in interview rounds of Amazon, MakeMyTrip, VMWare etc. 这个问题在亚马逊&#xff0c;MakeMyTrip&#xff0c;VMWare等访谈轮次中都有介绍。 Problem statement: 问题陈述&…

求根号m(巴比伦算法)

巴比伦算法是针对求根号m的近似值情况的&#xff0c;它的思想是这样的&#xff1a; 设根号mX0,则如果枚举有答案X(X<X0)&#xff0c;则m/X>X0,当精度要求不高的时候&#xff0c;我们可以看成Xm/XX0,而如果精度要求比较高&#xff0c;我们只需取X和m/X的平均值作为新的枚举…

算法题复习(快排、链表、二分、哈希、双指针)

目录1、快速排序复习2、链表部分复习203. 移除链表元素707. 设计链表206. 反转链表142.环形链表 II3、二分法复习4、哈希法复习5、双指针复习**15. 三数之和****18. 四数之和****27. 移除元素****344. 反转字符串**,简单&#xff0c;双指针从两侧往中间靠拢&#xff0c;并随时s…

码农干货系列【4】--图像识别之矩形区域搜索

简介 定位某个图片的矩形区域是非常有用的&#xff0c;这个可以通过手动的选择某个区域来实现定位&#xff0c;图片相关的软件都提供了这个功能&#xff1b;也可以像本篇一个通过程序来实现智能定位。前者会有误差&#xff0c;效率低下&#xff1b;后者选区精度高&#xff0c;效…

Android中的广播Broadcast详解

今天来看一下Android中的广播机制&#xff0c;我们知道广播Broadcast是Android中的四大组件之一&#xff0c;可见他的重要性了&#xff0c;当然它的用途也很大的&#xff0c;比如一些系统的广播&#xff1a;电量低、开机、锁屏等一些操作都会发送一个广播&#xff0c;具体的And…

.NET线程池

摘要 深度探索 Microsoft .NET提供的线程池&#xff0c; 揭示什么情况下你需要用线程池以及 .NET框架下的线程池是如何实现的&#xff0c;并告诉你如何去使用线程池。 内容 介绍 .NET中的线程池 线程池中执行的函数 使用定时器 同步对象的执行 异步I/O操作 监视线程池 死锁 有关…

《c++特性》

目录多态构造函数和析构函数存在多态吗&#xff1f;虚函数表虚析构函数纯虚函数和抽象类运行时多态和编译时多态的区别继承设计实例指针对象和普通对象的区别正确初始化派生类方式继承和赋值的兼容规则protected 和 private 继承基类与派生类的指针强制转换如何用C实现C的三大特…

Scala中的while循环

在Scala中的while循环 (while loop in Scala) while loop in Scala is used to run a block of code multiple numbers of time. The number of executions is defined by an entry condition. If this condition is TRUE the code will run otherwise it will not run. Scala中…

牛客网与leetcode刷题(高频题中简单or中等的)

目录1、反转链表2、排序3、先序中序后序遍历4、最小的k个数5、子数组的最大累加和6、 用两个栈实现队列7、142. 环形链表 II8、20. 有效的括号9、最长公共子串(动态规划),磕磕绊绊10、二叉树之字形层序遍历11、重建二叉树12、LRU缓存13、合并两个有序链表15、大数加法16、一个二…

AMUL的完整形式是什么?

AMUL&#xff1a;阿南德牛奶联盟有限公司 (AMUL: Anand Milk Union Limited) AMUL is an abbreviation of Anand Milk Union Limited. It is an Indian milk product cooperative dairy organization that is based in the small town of Anand in the state of Gujarat. AMUL …

mochiweb 源码阅读(十一)

大家好&#xff0c;今天周六&#xff0c;继续接着上一篇&#xff0c;跟大家分享mochiweb源码。上一篇&#xff0c;最后我们看到了mochiweb_socket_server:listen/3函数&#xff1a; listen(Port, Opts, State#mochiweb_socket_server{sslSsl, ssl_optsSslOpts}) ->case moch…

Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能 (转)

转载请注明出处&#xff1a;http://blog.csdn.net/guolin_blog/article/details/9255575 最 近项目中需要用到ListView下拉刷新的功能&#xff0c;一开始想图省事&#xff0c;在网上直接找一个现成的&#xff0c;可是尝试了网上多个版本的下拉刷新之后发现效果都不怎么理 想。有…