从STL标准库ALLOC中学习内存管理

本文深入探讨了STL中默认内存管理器的工作原理,包括如何处理不同大小的内存请求,并通过维护多个单链表来提高小块内存分配的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从STL标准库ALLOC中学习内存管理

STL学习的第一个话题就是内存分配,也是各类容器和算法的基础。每一个容器均配有一个内存分配器用于管理内存。一般情况下,我们没有必要去实现一个内存分配器,编译器会默认给配置一个功能很强大的内存管理器,也是我们学习的重点。默认的内存管理器有以下特点:

1、对大块内存和小块内存的申请,分别进行处理;例如大于128byte的调用malloc直接分配。小于128byte的维护一个内存池进行动态分配。

2、对小块内存的管理通过维护字节为8、16、24、32等不同长度的单链表进行动态的分配与回收,进一步提高效率并且减少内存片。

3、采用union结构产生单链表,减少next_ptr带来的额外内存消耗

模拟STL中对小块内存的管理,实现了一个对内存管理的程序,详细解释如下:

头文件:

// Created/Modified Time: Sat 13 Jul 2013 09:54:06 PM CST
// File Name: test_alloc.h
// Description:
#ifndef HOME_FANGQINGAN_FQA_PROGRAM_TEST_ALLOC_H_
#define HOME_FANGQINGAN_FQA_PROGRAM_TEST_ALLOC_H_
namespace test {
// 最小内存管理单位
const int kMinAlign = 8; // min alloc mem
const int kMaxAlign = 128;  // 最大内存管理单位
const int kNFreeList = kMaxAlign / kMinAlign;  // 需要维护的单链表的个数
const int kMinChunk = 20;  // 内存扩充时最小倍数
union obj {
  union obj* free_list_link;  // 内部使用free_list_link进行单链表的串接
  char clint_data[1];  // 给用户的数据
};
class MyAlloc {
 public:
  // 内存管理的两个函数,申请和释放
  static void *allocate(size_t n);
  static void deallocate(void *p, size_t n);
 private:
  static void *refill(size_t n);
  static char* chunk_alloc(size_t size, int& nobjs);
  // main vars
  static obj* free_list[kNFreeList];  // 单链表数组
  static char* start_free;//内存池开始位置 
  static char * end_free;//内存池结束位置
  static size_t heap_size;//已经分配内存大小
};
}

#endif // HOME_FANGQINGAN_FQA_PROGRAM_TEST_ALLOC_H_
源文件:

// Created/Modified Time: Sat 13 Jul 2013 10:04:22 PM CST
// File Name: test_alloc.cc
// Description:

#include "test_alloc.h"

namespace test {
// static vars
obj*MyAlloc::free_list[kNFreeList] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
char *MyAlloc::start_free = 0;
char* MyAlloc::end_free = 0;
size_t MyAlloc::heap_size = 0;
// 申请申请内存大小,确定使用哪个链表结构
size_t free_list_index(size_t bytes) {
  return (bytes + kMinAlign-1)/kMinAlign-1;
}
// 对于不足8字节的 进行提升
size_t round_up(size_t bytes) {
  return (bytes + kMinAlign - 1) & ~(kMinAlign-1);
}

// 分配内存
void* MyAlloc::allocate(size_t n) {
  if (n>kMaxAlign) {  // no deal
    return 0;
  }
  obj** my_free_list = free_list + free_list_index(n);
  obj* result = *my_free_list;
  if(result == 0) {
    // 如果某链表为空,则从内存池中分配一些到该链表
    void *r = refill(round_up(n));
    return r;
  }
  *my_free_list = result->free_list_link;
  return result;
}
void MyAlloc::deallocate(void *p, size_t n) {
  if (n > kMaxAlign)
    return;
  obj** my_free_list = free_list + free_list_index(n);
  obj* q = (obj*)p;
  q->free_list_link = my_free_list;
  *my_free_list = q;
}
// 用于填充空的链表结构
void *MyAlloc::refill(size_t n) {
  int nobjs = kMinChunks;
  // 从内存池中申请内存,nobjs为引用类型,返回能够申请到的内存块个数
  char * chunk = chunk_alloc(n, nobjs);
  obj** my_free_list;
  obj* current_obj, *next_obj;
  if(nobjs == 1)
    return chunk;
  my_free_list = free_list + free_list_index(n);
  obj* result = (obj*)chunk;
  *my_free_list = next_obj = (obj*)(chunk + n);
  // 将多余的内存块插入到链表中,等同于单链表的标头插入
  for (int i = 1;;i++) {
    current_obj = next_obj;
    next_obj = (obj*)((char*)next + n);
    if (nobjs - 1 == i) {
      current_obj->free_list_link = 0;
      break;
    } else {
      current_obj->free_list_link = next_obj;
    }
  }
  return result;
}
// 申请nobjs块空间,如果不够则从系统内存中获取
char *MyAlloc::chunk_alloc(size_t size, int& nobjs) {
  char *result;
  size_t total_bytes = size* nobjs;
  size_t left_bytes = end_free - start_free;
  if (left_bytes >= total_bytes) {
    result = start_free;
    start_free += total_bytes;
    return result;
  }
  // 有多少分配多少
  if (left_bytes >= size) {
    nobjs = left_bytes / size;
    total_bytes = size * nobjs;
    result = start_free;
    start_free += total_bytes;
    return result;
  }
  // 每次从系统申请空间时,多申请一些,至少为需求的两倍。
  size_t bytes_to_get = 2* total_bytes + round_up(heap_size) >> 4;
  if (left_bytes > 0) {
    obj**my_free_list = free_list + free_list_index(left_bytes);
    (obj*)start_free->free_list_link = *my_free_list;
    *my_free_list = (obj*)start_free;
  }
  start_free = (char *)malloc(bytes_to_get);
  if (start_free == 0) {
    nobjs = 0;
    return 0;
  }
  end_free = start_free = bytes_to_get;
  heap_size += bytes_to_get;
  chunk_alloc(size, nobjs);
}
}

总结

对内存池管理的目的减少大量申请和释放空间给程序带来时间开销,同时也能够减少内存碎片。实际应用中可以直接通过对象池的方法,对实际对象进行管理。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值