c++内存管理 第二讲std_allocator

一、malloc()内部原理

1、VC6.0 malloc

工业海量的小区块,cookie会导致浪费很大

从上图可见,VC6中的malloc()函数分配的内存里面除了我们需要申请的内存空间外还有cookie,debug信息和pad,其中cookie是我们不需要的,如果大量调用malloc的话cookie总和会增多,这回造成较大的浪费。

从上面可以看出,VC6.0的allocate()函数只是对malloc的二次封装,并没有做什么很特殊的操作,它是以类型字节长度为单位分配内存的,上图就分配了512个int类型空间。

2、BC5 malloc

BC5的allocate()函数和VC6.0本质一样,allocate()函数只是对malloc的二次封装,并没有做什么很特殊的操作。

3、G2.9 malloc

GCC 2.9版本的allocator如上图所示,但是在实际中该部分却没有被包含使用,从下图**容器使用的Alloc可以看到,实际的分配器是使用了一个叫alloc的类,该类分配内存是以字节为单位的,而不是以对象为单位。**下图右边灰色部分分配的是512字节,而不是512个对象。

4、__pool_alloc

在GCC 4.9版本,2.9版本的allocate不属于正式使用的那个版本,而是变成了__pool_alloc

从上面两张图可以对比看出,2.9版本的allocate和4.9版本的__pool_alloc做的事是一样的,只是修改了变量名和一些细小操作而已。

从上面的测试结果可以看出,如果使用了__pool_alloc的话,连续两块内存之间的距离是8,而一个double类型变量的大小也是8个字节,说明这连续几块内存之间是不带cookie的(即使这几块内存在物理上也是不连续的)。如果使用std的allocator,那么相邻两块内存之间距离为16个字节,每块内存带有一个4字节的头和4字节的尾。

二、std::alloc

1、std:alloc运作模式

std::alloc使用一个16个元素的数组来管理内存链表,而我们上一章只是用了一条链表。数组不同的元素8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128管理不同的区块管理,例如#3号元素负责管理32bytes为一小块的链表。

假设现在用户需要32字节的内存,std::allloc先申请一块区间,为32*20(20个b经验值)*2大小,用一条链表管理,然后让数组的#3元素管理这条链表。接着讲该以32为一个单元的链表的一个单元(32字节)分给用户。为什么是32*20*2?
前面32*20空间是分配给用户的,但是后面的32*20空间是预留给pool的,如果这时用户需要一个64字节的空间,那么剩下的32*20空间将变成64*10,然后将其中64字节分配给用户,而不用再一次地构建链表和申请空间

但是也有上限。如果该链表组维护的链表最大的一个小块为128byte,但是用户申请内存块超过了128byte,那么std::alloc将调用malloc给用户分配空间,然后该块将带上cookie头和尾。

在真正的商业级的内存分配器中,一般都会使用嵌入式指针,借用每一个小块的前四个字节用作指针连接下一块可用的内存块。

2、std::alloc运行一瞥

roundup追加量 目前的申请量/16在调到8的边界,越要越大

注意这里将80拨给了第9号

如果分到最后没有内存(机器已经不能给分配空闲内存给你了),

3、std::alloc源码剖析

原版ppt的1-3张介绍的是GCC 2.9的std::alloc的第一级分配器,这里先从第二级开始分析,然后再到第一级。

该分配器为__default_alloc_template,一开始默认使用的分配器,在该类中定义了ROUND_UP函数,用来将申请内存数量做16字节对齐。定义了union free_list_link,在后面会介绍它的作用,在上一章中我们构建的一个小的分配器中也定义了该联合体,作用类似,该联合体可以使用struct代替。free_list是一个有16个obj*元素的数组,在前面讲过,GCC 2.9的分配器用一个16字节数组管理16条链表,free_list便是该管理数组。**refill(充值)和chunk_alloc(要一大块)**在后面再介绍。start_free和end_free分别指向该内存池的头和尾

deallocate没有free,所以deallocate就把拥有过的资源一直拿着(把删除的东西擦掉,下次再要就给你了),不算内存泄漏

首先看allocate函数,在函数的一开始便定义了:

obj* volatile *my_free_list;//volatile多线程相关的,不用管

结合上图右侧的链表图和上上一张图片内容,my_free_list指向的是free_list中16个元素中的任何一个,my_free_list则取出free_list某元素中的值,该值指向一条分配内存的链表。所以my_free_list要定义为二级指针

result则保存分配给用户的一块内存的地址。

首先:

if (n > (size_t)__MAX_BYTES) {
    return(malloc_alloc::allocate(n));
}

检查用户申请内存块大小,如果大于__MAX_BYTES(128)那么将调用malloc_alloc::allocate(),这便是第一级分配器,这在后面分析。现在假设用户申请内存小于128字节,那么将根据用户申请内存大小分配对应的内存,由于内存池使用free_list链表管理的,每个free_list链表元素管理不同的内存块大小,这在前面介绍过了。于是有:

my_free_list = free_list + FREELIST_INDEX(n);

定位到该内存块的位置,这时my_free_list指向的是管理该内存块的空间的地址,使用*my_free_list便可以取到该内存块的地址:

result = *my_free_list;

然后判断result是否为空:

if (result == 0) {
    void* r = refill(ROUND_UP(n));
    return r;
}

如果为空,说明系统内存不够用了,将使用refill()函数分配内存,这部分在后面会介绍。

如果情况正常,那么将该链表中下一个可以使用的空间设置为当前分配给用户空间指向的下一个、在逻辑上连续的空间,最后将result返回给用户:

*my_free_list = result->free_list_link;
return (result);

下面的这张图很形象地演示了内存分配的过程:

接下来分析释放内存。

释放内存的代码也不难理解,找到需要释放内存的那块空间的地址,然后将当前可分配给用户的空间地址设置为需要释放的该内存空间,一开始指向的可分配的内存空间地址赋值给需要释放空间地址的逻辑连续的下一个内存地址。感觉十分拗口,图和代码更能体现这一过程:

上面说到,不论是分配内存还是释放内存,则有:

if (n > (size_t)__MAX_BYTES) {
    return(malloc_alloc::allocate(n));
}

和:

if (n > (size_t) __MAX_BYTES) {
    malloc_alloc::deallocate(p, n);
    return;
}

也就是将内存分配与释放操作放到第一级allocator中

从上图中可以看到,第一级分配器叫做:

class __malloc_alloc_template

其实有:

typedef __malloc_alloc_template<0>  malloc_alloc;

这在后面会介绍。

分配器的allocate函数如下:

  static void* allocate(size_t n)
  {
    void *result = malloc(n);   //直接使用 malloc()
    if (0 == result) result = oom_malloc(n);
    return result;
  }

直接调用malloc函数分配内存,如果分配失败则调用oom_malloc函数。

同样地,reallocate也是如此:

  static void* reallocate(void *p, size_t /* old_sz */, size_t new_sz)
  {
    void * result = realloc(p, new_sz); //直接使用 realloc()
    if (0 == result) result = oom_realloc(p, new_sz);
    return result;
  }

如果重新要求内存失败,则调用oom_realloc函数,这两个函数在后续会介绍。

deallocate操作则直接释放内存:

static void deallocate(void *p, size_t /* n */)
{
	free(p);                    //直接使用 free()
}

set_malloc_handler是个函数指针,里面传入一个void (*f)()类型函数:

  static void (*set_malloc_handler(void (*f)()))()
  { //類似 C++ 的 set_new_handler().
    void (*old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return(old);
  }

该函数设置的是内存分配不够情况下的错误处理函数,这个需要交给用户来管理,首先保存先前的处理函数,然后再将新的处理函数f赋值给__malloc_alloc_oom_handler,然后返回旧的错误处理函数,这也在下一张图片中会介绍:

到这里,分配器只剩下refill函数没有分析了,下面将重点讨论该函数。不过在讨论refill函数之前有必要分析chunk_alloc函数:

建议写0==start_free,避免把0赋值给start_free

192~207不推荐,建议使用时定义

使用带有pool的比较好的,数据量虽然更大,但中间可能delete,countnew是累计使用过多少。

使用带有pool的比较好的,调用次数很小

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值