1.引入内存池的意义
内存池(Memory Pool)是一种内存分配方式,又被称为固定大小区块规划(fixed-size-blocks allocation)。通常我们习惯直接使用new、malloc等API申请分配内存,但是这种方式非常容易产生内存碎片,早晚都会申请内存失败。并且在比较复杂的代码或者继承的屎山中,非常容易出现内存泄漏。
内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是能够使内存分配效率得到提升。
2.内存池的实现原理
我们通过一个内存池类来操作内存池,这个类中的所有成员方法都是操作内存池的函数。在我们设想的内存池中,我们以4k为基准定义,申请空间超过4k的我们额外使用malloc分配一个自定义块,并且使用一个管理自定义块的对象来保存它的地址,以便于在释放空间的时候我们能够找到他。申请空间不超过4k的我们就将我们内存池的标准块中的一部分划分给它来减少malloc的次数,若当前标准块的空间不够,则额外开辟一块4k的空间加入内存池并且给它分配空间。
2.1 内存池实现的主要功能
在此,笔者将简述一下内存池类(Mem_pool)的成员函数及其实现的主要功能:
Mem_pool(size_t size) :构造函数,创建内存池,并初始化一块自定义大小的空间
~Mem_pool() :析构函数,销毁内存池,遍历各个标准块与大块,把从堆区分配的内存释放
mp_new(size_t size) :给用户提供的申请内存空间的接口
mp_new_zero(size_t size) :给用户提供的申请内存空间的接口,并全部初始化为0
mp_delete(void *p) :给用户提供的释放已申请的内存空间的接口
mp_reset_pool() :给用户提供的清空内存池中数据的接口
mp_monitor(string msg) :给用户提供的显示当前内存池使用情况的接口
mp_new_block(size_t size) :向堆空间申请一块标准块空间
mp_new_large(size_t size) :向堆空间申请一块长度大于标准块长度的自定义大块空间
2.2 内存池空间分配的实现原理
在管理内存池的类(Mem_pool)中,存在着两个链表,其中一个是以管理标准块的类为数据域的不带表头结点的单链表,另一个是以管理自定义块的类为数据域的不带表头的单链表。
一个管理标准快的对象(Mem_node)管理一个标准块,且该对象的起始地址为该标准块的起始地址。Mem_node类有一个起始指针,一个结束指针与一个next指针,起始指针指向的是该标准块能够分配内存的起始地址,结束指针指向该标准块的结束地址,next指针用来指向下一个Mem_node对象用来形成链表。
一个管理自定义块的对象(Mem_large)管理一个自定义块,每当我们创建一个自定义块的时候我们才创建一个Mem_large对象来管理它,并且该对象在内存池的标准块中申请空间(换句话说就是Mem_large对象的内存空间由内存池的标准快分配)。Mem_large类有一个指向自定义块的指针,用来以后释放空间,还有一个next指针用来形成链表。
每当我们需要申请一块空间的时候,我们首先需要判断这块空间的大小是否大于一个标准块的大小(4k),若没有超过我们就将在标准块中分配这段空间。
假设现在我们在标准块中分配,我们还需要判断我们当前的标准块剩余的可用空间能否容纳下我们需要分配的空间。若可以,我们就将在当前的标准块中划分出空间来实现空间分配,并且减少当前标准块可以分配的空间,如图1所示。若不可以,我们将开辟出一块新的标准块,并实现上述操作,如图2所示。
图1 标准块剩余空间足够的情况
图2 标准块剩余空间不足需要开辟新的标准块的情况
假设我们需要分配的空间超过了4k,我们就需要开辟出一个能够容纳该空间的自定义块,将自定义块的内存空间分配出去,接着我们还需要创建一个管理自定义块的对象,这个对象的一个成员指针保存这段被分配的空间的地址用来便于以后释放,如图3所示。
图3 创建自定义块的情况
随着标准块的创建,我们也将创建一个管理该标准块的对象(Mem_node),并将其挂载在管理内存池的对象(Mem_pool)的链表中,所以我们只需要找到内存池就可以找到所有在内存池中分配的标准块。
随着自定义块的创建,该管理该自定义块的对象将被挂载在管理内存池的对象(Mem_pool)的链表中,所以我们只需要找到内存池就可以找到所有在内存池中分配的自定义块。
2.3 内存池空间释放的实现原理
当我们想要释放一段从内存池中申请的内存空间时,我们需要判断这段空间是存在在标准块中还是自定义块中。
若存在于自定义块中,我们将遍历内存池对象中的管理自定义块对象的链表,找到对应的Mem_large对象后,我们只需要释放该对象中指向自定义块的指针就可以实现自定义块空间的释放了。
若存在于标准块中,我们也需要遍历内存池对象中管理标准块对象的链表,找到对应的Mem_node对象后,我们需要将该对象的引用计数(m_quote)减一(这样的话就可以当作该内存空间已经被释放掉了)。此处需要补充一下,在我们在标准块中申请空间成功时,我们将在增加管理该块的对象中的引用计数,所以该对象的引用计数就是我们在该标准块中申请的内存空间个数。当该块的引用计数为0的时候,我们就可以把这个标准块当作一个刚刚创建的标准块来给其他申请空间的指针来分配空间了。
提醒:内存池的释放仅进行内存空间的释放,为了避免错误操作内存导致程序错误的情况,请一定记得在释放完空间之后将指针置为nullptr。
3、 对new与delete的重载
在C++中我们可以对new与delete进行重载实现对堆空间内存申请操作的接管。 这样的话我们就可以几乎感觉不到任何区别地来使用内存池来申请与释放内存空间了。需要另外提示的一点是我们在对new重载之后,依然可以在new后面对类类型进行初始化从而进入到类的构造函数中,例如:Test * p = new Test(10);
void *operator new(size_t size) { return mp.mp_new(size); } void operator delete(void *p) { mp.mp_delete(p); }
4. C++内存池对纯C内存池的增强
在C++内存池中,我们将内存池封装成了一个对象,通过这个对象来操作内存池,通过提供成员函数接口的操作隐藏了具体的实现代码,可以更加安全与简洁地操作内存池。
另外,将内存池封装成类还可以遵循RAII原则,我们在内存池对象的构造函数里面对内存池进行初始化,在内存池的析构函数里面销毁内存池并且将内存池从内存中申请的所有空间释放,避免了内存泄漏的情况发生。
5、测试如下
class Test { public: Test() = delete; Test(int a, double b, string c); private: int m_a; double m_b; string m_c; }; Test::Test(int a, double b, string c) : m_a(a), m_b(b), m_c(c) { cout << "Test构造!\n"; } int main() { char * p = nullptr; string s1 = "show"; mp.mp_monitor(s1); for(int i =0 ;i <100;i++) { p = (char *)mp.mp_new(50); } mp.mp_monitor(s1); for(int i =0 ;i <20;i++) { p = (char *)mp.mp_new(60); } mp.mp_monitor(s1); p = (char *)mp.mp_new(5000); mp.mp_monitor(s1); mp.mp_delete(p); mp.mp_monitor(s1); Test * p1 = new Test(1,2.0,s1); mp.mp_monitor(s1); return 0; }
6、源码
1 #include <iostream> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <string> 5 6 #define PAGE_SIZE 4096 7 8 using uchar = unsigned char; 9 using namespace std; 10 11 //对齐规则 12 #define MP_ALIGNMENT 16 13 #define mp_align(n, alignment) (((n) + (alignment - 1)) & ~(alignment - 1)) 14 #define mp_align_ptr(p, alignment) (void *)((((size_t)p) + (alignment - 1)) & ~(alignment - 1)) 15 16 17 class Mem_node 18 { 19 public: 20 //初始化node结点 21 void init(); 22 //将node结点置回初始状态 23 void node_clean(); 24 //分配成功,引用计数+1,移动start指针 25 void node_malloc(size_t size); 26 //减少引用计数,若为0移动start指针 27 void node_free(); 28 uchar * get_start(); 29 uchar * get_end(); 30 int get_quote(); 31 //获取end - start的值,也就是当前块现在的可用长度 32 size_t get_len(); 33 Mem_node *get_next(); 34 //每次调用get方法failed计数器自增1 35 int get_failed(); 36 void set_next(Mem_node* next); 37 38 private: 39 uchar *m_start; //标记当前标准块可以使用的起始地址 40 uchar *m_end; //标记当前标准块可以使用的末尾地址 41 Mem_node *m_next; //标准快链表的next指针 42 int m_quote; //当前标准块的引用计数器,用来判断当前标准块是否被使用 43 int m_failed; //当前标准块的错误计数器,用来移动内存池的current指针提高效率 44 }; 45 46 47 class Mem_large 48 { 49 public: 50 //使large结点的成员变量重置为参数所示的值 51 void large_reset(int size,void * alloc); 52 //释放掉large结点指向的自定义块内存空间 53 void large_free(); 54 void set_next(Mem_large * next); 55 Mem_large * get_next(); 56 void * get_alloc(); 57 int get_size(); 58 private: 59 Mem_large *m_next; //自定义块链表的next指针 60 int m_size; //用来存放自定义块所占用的内存大小 61 void *m_alloc; //用来存放自定义块所在的起始地址 62 }; 63 64 65 class Mem_pool 66 { 67 public: 68 Mem_pool() = delete; 69 70 //构造函数,创建内存池,并初始化一块自定义大小的空间 71 explicit Mem_pool(size_t size); //类型转化构造函数,自定义的大小仅允许是4k的整数倍 72 73 //析构函数,销毁内存池,遍历各个标准块与大块,把从堆区分配的内存释放 74 ~Mem_pool(); 75 76 //给用户提供的申请内存空间的接口 77 void *mp_new(size_t size); 78 79 //给用户提供的申请内存空间的接口,并全部初始化为0 80 void *mp_new_zero(size_t size); 81 82 //给用户提供的释放已申请的内存空间的接口 83 void mp_delete(void *p); 84 85 //给用户提供的清空内存池中数据的接口 86 void mp_reset_pool(); 87 88 //给用户提供的显示当前内存池使用情况的接口 89 void mp_monitor(string msg); 90 91 private: 92 //向堆空间申请一块标准块空间 93 void *mp_new_block(size_t size); 94 95 //向堆空间申请一块长度大于标准块长度的自定义大块空间 96 void *mp_new_large(size_t size); 97 98 Mem_node *m_head; //标准块链表的头指针 99 Mem_node *m_current; //标准块链表当前可能使用的指针(局部性原理) 100 Mem_large *m_large; //大块链表的头指针 101 }; 102 103 Mem_pool mp(4096); 104 105 void *operator new(size_t size) 106 { 107 return mp.mp_new(size); 108 } 109 110 void operator delete(void *p) 111 { 112 mp.mp_delete(p); 113 } 114 115 116 117 118 void Mem_node::init() 119 { 120 m_start = (uchar *)this + sizeof(Mem_node); 121 m_end = (uchar *)this + PAGE_SIZE; 122 m_next = nullptr; 123 m_failed = 0; 124 m_quote = 0; 125 } 126 127 //将node结点置回初始状态 128 void Mem_node::node_clean() 129 { 130 m_start = (uchar *)this + sizeof(Mem_node); 131 m_failed = 0; 132 m_quote = 0; 133 } 134 135 //分配成功,引用计数+1,移动start指针 136 void Mem_node::node_malloc(size_t size) 137 { 138 m_quote++; 139 m_start += size; 140 } 141 142 //减少引用计数,若为0移动start指针 143 void Mem_node::node_free() 144 { 145 m_quote--; 146 } 147 148 uchar *Mem_node::get_start() 149 { 150 return m_start; 151 } 152 153 uchar *Mem_node::get_end() 154 { 155 return m_end; 156 } 157 158 int Mem_node::get_quote() 159 { 160 return m_quote; 161 } 162 163 //获取end - start的值,也就是当前块现在的可用长度 164 size_t Mem_node::get_len() 165 { 166 return (m_end - m_start); 167 } 168 169 Mem_node *Mem_node::get_next() 170 { 171 return m_next; 172 } 173 174 //每次调用get方法failed计数器自增1 175 int Mem_node::get_failed() 176 { 177 m_failed++; 178 return m_failed; 179 } 180 181 void Mem_node::set_next(Mem_node *next) 182 { 183 m_next = next; 184 } 185 186 void Mem_large::large_reset(int size, void *alloc) 187 { 188 m_size = size; 189 m_alloc = alloc; 190 } 191 192 void Mem_large::large_free() 193 { 194 free(m_alloc); 195 m_size = 0; 196 m_alloc = nullptr; 197 } 198 199 void Mem_large::set_next(Mem_large *next) 200 { 201 m_next = next; 202 } 203 204 Mem_large *Mem_large::get_next() 205 { 206 return m_next; 207 } 208 209 void *Mem_large::get_alloc() 210 { 211 return m_alloc; 212 } 213 214 int Mem_large::get_size() 215 { 216 return m_size; 217 } 218 219 //构造函数,创建内存池,并初始化一块自定义大小的空间 220 Mem_pool::Mem_pool(size_t size) 221 { 222 if (size < PAGE_SIZE || size % PAGE_SIZE != 0) 223 { 224 size = PAGE_SIZE; 225 } 226 //posix_memalign((void **)&pool, MP_ALIGNMENT, size); 227 //m_current = (Mem_node *)malloc(size); 228 posix_memalign((void **)&m_current, MP_ALIGNMENT, size); 229 m_head = m_current; 230 m_head->init(); 231 m_large = nullptr; 232 } 233 234 //析构函数,销毁内存池,遍历各个标准块与大块,把从堆区分配的内存释放 235 Mem_pool::~Mem_pool() 236 { 237 Mem_large *large = m_large; 238 Mem_node *cur, *next; 239 240 for (large; large != nullptr; large = large->get_next()) 241 { 242 if (large->get_alloc() != nullptr) 243 { 244 free(large->get_alloc()); 245 } 246 } 247 248 cur = m_head; 249 while (cur != nullptr) 250 { 251 next = cur->get_next(); 252 free(cur); 253 cur = next; 254 } 255 } 256 257 //给用户提供的申请内存空间的接口 258 void *Mem_pool::mp_new(size_t size) 259 { 260 if (size <= 0) 261 { 262 return nullptr; 263 } 264 else if (size >= PAGE_SIZE - sizeof(Mem_node)) 265 { 266 return mp_new_large(size); 267 } 268 else 269 { 270 uchar *addr = nullptr; 271 Mem_node *cur = m_current; 272 273 while (cur != nullptr) 274 { 275 if (cur->get_len() >= size) 276 { 277 addr = cur->get_start(); 278 cur->node_malloc(size); 279 return addr; 280 } 281 else 282 { 283 cur = cur->get_next(); 284 } 285 } 286 return mp_new_block(size); 287 } 288 } 289 290 //给用户提供的申请内存空间的接口,并全部初始化为0 291 void *Mem_pool::mp_new_zero(size_t size) 292 { 293 void *mem_addr = mp_new(size); 294 if (mem_addr != nullptr) 295 { 296 memset(mem_addr, 0, size); 297 } 298 return mem_addr; 299 } 300 301 //给用户提供的释放已申请的内存空间的接口 302 void Mem_pool::mp_delete(void *p) 303 { 304 Mem_large *large = m_large; 305 Mem_node *node = nullptr; 306 307 //若需要删除的是自定义块 308 for (large; large != nullptr; large = large->get_next()) 309 { 310 if (p == large->get_alloc()) 311 { 312 large->large_free(); 313 return; 314 } 315 } 316 317 //若要删除的是标准块 318 node = m_head; 319 for (node; node != nullptr; node = node->get_next()) 320 { 321 if (((uchar *)node <= (uchar *)p) && ((uchar *)p <= (uchar *)node->get_end())) 322 { 323 node->node_free(); 324 if (node->get_quote() == 0) 325 { 326 m_current = m_head; 327 } 328 return; 329 } 330 } 331 } 332 333 //给用户提供的清空内存池中数据的接口 334 void Mem_pool::mp_reset_pool() 335 { 336 Mem_node *node = nullptr; 337 Mem_large *large = nullptr; 338 339 for (large = m_large; large != nullptr; large = large->get_next()) 340 { 341 if (large->get_alloc() != nullptr) 342 { 343 free(large->get_alloc()); 344 } 345 } 346 347 for (node = m_head; node != nullptr; node = node->get_next()) 348 { 349 node->node_clean(); 350 } 351 m_current = m_head; 352 } 353 354 //给用户提供的显示当前内存池使用情况的接口 355 void Mem_pool::mp_monitor(string msg) 356 { 357 Mem_node *head = nullptr; 358 Mem_large *large = nullptr; 359 int i = 0; 360 361 printf("\r\n\r\n------start monitor poll------%s\r\n\r\n", msg.c_str()); 362 for (head = m_head; head != nullptr; head = head->get_next()) 363 { 364 i++; 365 if (m_current == head) 366 { 367 cout << "current==>第" << i << "块\n"; 368 } 369 370 printf("第%02d块small block 已使用:%4ld 剩余空间:%4ld 引用:%4d failed:%4d\n", i, 371 head->get_start() - (uchar *)head, 372 head->get_len(), head->get_quote(), head->get_failed()); 373 } 374 375 i = 0; 376 for (large = m_large; large != nullptr; large = large->get_next()) 377 { 378 i++; 379 if (large->get_alloc() != nullptr) 380 { 381 cout << "第" << i << "块large block size=" << large->get_size() << "\n"; 382 } 383 } 384 printf("\r\n\r\n------stop monitor poll------\r\n\r\n"); 385 } 386 387 //向堆空间申请一块标准块空间 388 void *Mem_pool::mp_new_block(size_t size) 389 { 390 int ret; 391 uchar *block = nullptr; 392 uchar *addr = nullptr; 393 Mem_node *node = nullptr; 394 Mem_node *current = m_current; 395 Mem_node *cur = nullptr; 396 397 //int ret = posix_memalign((void **)&block, MP_ALIGNMENT, PAGE_SIZE); 398 //block = (uchar *)malloc(PAGE_SIZE); 399 ret = posix_memalign((void **)&block, MP_ALIGNMENT, PAGE_SIZE); 400 if (ret != 0) 401 { 402 cout << "malloc error" << endl; 403 return nullptr; 404 } 405 406 node = (Mem_node *)block; 407 node->init(); 408 409 addr = (uchar *)mp_align_ptr(block + sizeof(Mem_node), MP_ALIGNMENT); 410 node->node_malloc(size); 411 412 for (cur = current; cur->get_next() != nullptr; cur = cur->get_next()) 413 { 414 if (cur->get_failed() > 4) 415 { 416 current = cur; 417 } 418 } 419 420 cur->set_next(node); 421 m_current = current; 422 return addr; 423 } 424 425 //向堆空间申请一块长度大于标准块长度的自定义大块空间 426 void *Mem_pool::mp_new_large(size_t size) 427 { 428 uchar *big_addr = nullptr; 429 Mem_large *large = nullptr; 430 int ret; 431 int n = 0; 432 433 ret = posix_memalign((void **)&big_addr, MP_ALIGNMENT, size); 434 if (ret) 435 { 436 return nullptr; 437 } 438 439 large = m_large; 440 for (large; large != nullptr; large = large->get_next()) 441 { 442 if (large->get_alloc() == nullptr) 443 { 444 large->large_reset(size, big_addr); 445 return big_addr; 446 } 447 if (n++ > 3) //防止过多次的遍历,限制次数 448 { 449 break; 450 } 451 } 452 453 large = (Mem_large *)mp_new(sizeof(Mem_large)); 454 455 if (large == nullptr) 456 { 457 free(big_addr); 458 return nullptr; 459 } 460 461 large->large_reset(size, big_addr); 462 //头插法 463 large->set_next(m_large); 464 m_large = large; 465 466 return big_addr;Mymemory.cpp
标签:node,get,Mem,C++,large,内存,Linux,size From: https://www.cnblogs.com/gunancheng/p/17747751.html