C++内存管理机制(侯捷)笔记2

C++内存管理机制(侯捷)

本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。

参考链接

Youtube: 侯捷-C++内存管理机制

Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus

下面是第二讲allocator具体实现的笔记。

文章目录

  • C++内存管理机制(侯捷)
    • 17 VC6 malloc
    • 18 VC6标准分配器之实现
    • 19 BC5标准分配器之实现
    • 20 G2 9标准分配器之实现
    • 21 G2.9 std::alloc VS G4.9 __pull_alloc
    • 22 G4.9 __pull_alloc用例
    • 23 G2.9 std::alloc
    • 24 G2.9 std::alloc运行一瞥01-05
    • 25 G2.9 std::alloc运行一瞥06-10
    • 26 G2.9 std::alloc运行一瞥11-13
    • 27 G2.9 std::alloc源码剖析(上)
    • 28 G2.9 std::alloc源码剖析(中)
    • 29 G2.9 std::alloc源码剖析(下)
    • 30 G2.9 std::alloc观念大整理
    • 31 G4.9 pull allocator运行观察
    • 后记

17 VC6 malloc

VC6下的malloc内存块布局:从上往下分别是cookie,debug header, 实际数据的block, debug tail, pad, cookie,对应于下图。

在这里插入图片描述

在VC6(Visual C++ 6.0)下,malloc 函数分配的内存块的布局包含以下部分:

  1. Cookie(饼干)

    • Cookie 是一小段额外的标识信息,用于在边界检查中检测缓冲区溢出。它通常是一个特殊的值,放置在分配的内存块的开始位置。
  2. Debug Header(调试头部)

    • Debug Header 包含一些调试信息,例如分配的文件名、行号等。这些信息用于调试和跟踪内存分配的源。
  3. 实际数据的 Block

    • 这是分配的实际数据块,用于存储程序员请求的数据。
  4. Debug Tail(调试尾部)

    • Debug Tail 包含与调试信息相关的尾部数据。类似于 Debug Header,它包含一些额外的调试信息。
  5. Pad(填充)

    • 填充是为了确保分配的内存块满足特定的对齐要求。它可能包含一些额外的字节,使得整个内存块的大小满足特定的对齐条件。
  6. Cookie(饼干)

    • 与开头的 Cookie 相对应,是分配的内存块的结束位置。

这样的内存布局在 VC6 中用于调试和检测内存溢出等问题。Cookie 和调试信息是为了帮助调试过程,检测潜在的内存错误。在实际的发布版本中,这些额外的调试信息通常会被省略,以减小内存开销。 VC6 是相对较老的版本,现代的 Visual C++ 版本可能采用了更高效和精简的内存分配方案。

18 VC6标准分配器之实现

VC6标准分配器allocator的实现,里面的allocate和deallocate的实现只是调用::operator new 和::operator delete, 没有任何特殊设计。我们知道它们底层是malloc和free,所以具体分配的时候是带有cookie的,存在内存浪费的现象。

这里分配的单位是具体的类型,比如allocator<int>().allocate(512,(int*)0);分配的就是512个int类型的大小。而后面讲到的一个分配器是以字节为单位,不是以具体类型的大小为单位。

在这里插入图片描述

19 BC5标准分配器之实现

Borland5 编译器的allocator的实现,里面的allocate和deallocate的实现只是调用::operator new 和::operator delete, 没有任何特殊设计。

在这里插入图片描述

20 G2 9标准分配器之实现

GNU C++2.9标准分配器std::allocator的实现,里面的allocate和deallocate的实现只是调用::operator new 和::operator delete, 没有任何特殊设计。

在这里插入图片描述

GNU C++2.9容器使用的分配器,不是std::allocator,而是std::alloc

void* p = alloc::allocate(512); // 分配512bytes,不是512个int或者512个double类型等等

在这里插入图片描述

21 G2.9 std::alloc VS G4.9 __pull_alloc

G2.9 std::alloc在G4.9中是 __pull_alloc

在这里插入图片描述

G4.9版本中标准分配器std::allocator的实现,里面的allocate和deallocate的实现只是调用::operator new 和::operator delete, 没有任何特殊设计。

在这里插入图片描述

22 G4.9 __pull_alloc用例

这里的alloc(G2.9的叫法, G4.9叫做__pull_alloc)用法, 它在__gnu_cxx这个命名空间内。

vector<int, __gnu_cxx::__pool_alloc<int>> vecPool;

这个分配器是去除了cookie(一个元素带有上下两个cookie,共8B),可以省很多的内存空间。

在这里插入图片描述

23 G2.9 std::alloc

alloc的运作模式

上文C++内存管理机制(侯捷)笔记1介绍的是Per class allocator,每个类里面重载了 operator new和operator delete,每个类都单独维护一个链表。

下面如下图所示,是std::alloc为所有的类维护16个链表,每个链表负责不同大小的区块分配,从小到大分别负责8B, 16B, 24B, 32B,…, 按8的倍数增长,最大是16 x 8 = 128B。当大小不为8的倍数的时候,分配器会自动将其对齐到8的倍数。当大小超过128B时,就交给malloc来单独处理。

当一个vector里面存了一个“stone”类型的对象,它的大小是32B,那么就会启用下面的#3的链表,它负责32B区块的分配。每次分一大块(里面有20个小块,每个都是32B,但是实际分配的时候是20个小块的两倍大小,剩余的部分暂且不用), 指定一个小块给stone即可。

在这里插入图片描述

嵌入式指针embedded pointers

嵌入式指针工作原理:借用A对象所占用的内存空间中的前4个字节,这4个字节用来 链住这些空闲的内存块;
但是,一旦某一块被分配出去,那么这个块的 前4个字节 就不再需要,此时这4个字节可以被正常使用;

参考:https://blog.csdn.net/qq_42604176/article/details/113871565

内存的使用方法是,使用的时候看成对象实例,空闲的时候会看成next指针.

参考:https://github.com/KinWaiYuen/KinWaiYuen.github.io/blob/master/c%2B%2B/%E4%BE%AF%E6%8D%B7%E5%86%85%E5%AD%98.md

在这里插入图片描述

24 G2.9 std::alloc运行一瞥01-05

下面的free_list大小__NFREELISTS为16


enum {__ALIGN = 8};                        //小區塊的上調邊界
enum {__MAX_BYTES = 128};                  //小區塊的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; //free-lists 個數// union obj {                   //G291[o],CB5[x],VC6[x]
//   union obj* free_list_link;  //這麼寫在 VC6 和 CB5 中也可以,
// };                            //但以後就得使用 "union obj" 而不能只寫 "obj"
typedef struct __obj {struct __obj* free_list_link;
} obj;char*   start_free = 0;
char*   end_free = 0;
size_t  heap_size = 0;
obj* free_list[__NFREELISTS]= {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
# enum {__ALIGN = 8};  
size_t ROUND_UP(size_t bytes) {return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}

解释ROUND_UP

这段代码定义了一个常量 __ALIGN,它表示内存分配时的对齐边界,设定为 8 字节。然后,定义了一个函数 ROUND_UP,该函数接受一个大小(bytes)作为参数,然后将其上调到对齐边界。

具体来说,ROUND_UP 函数的作用是将传入的大小 bytes 上调到 __ALIGN 的倍数。这是通过以下步骤实现的:

  1. bytes 加上 __ALIGN-1,即将 bytes + 7
  2. 对结果进行按位与操作,通过 & ~(__ALIGN - 1) 将最低的 3 位清零,确保结果是 __ALIGN 的倍数。

这样做的目的是为了满足内存对齐的要求。在一些硬件体系结构中,访问未对齐的内存可能会导致性能问题或者错误,因此内存分配时通常需要对齐到某个特定的边界。这个函数就提供了一种简单的方法来确保分配的内存大小是对齐的。

在这里插入图片描述

当一个容器里面存储的元素大小是32B时,它的运作过程如下:

挂在几号链表呢?32 / 8 - 1 = 3号链表

刚开始的时候pool为空,此时要分配32 * 20 *2 + RoundUp(0 >> 4)= 1280B大小的空间为pool,然后从pool里面切割20个小块(共640B)挂在#3链表上,剩下的640B放在pool里面备用。RoundUp是追加量,里面的大小是把上次的累计申请量右移4位(除以16),由于这里是刚开始,没有累计申请量,故为0>>4。

在这里插入图片描述

此时,又创建了新的容器,它里面的元素大小为64B(对应#7链表),此时链表为空。由于上页的pool里面还剩640B,现在将其切分为640/64 = 10个区块,第一个给容器,剩下的9个挂在list #7. 此时pool的大小为0,因为被切分挂到链表上了。

在这里插入图片描述

在上面的基础上,现在新建容器,它的元素大小为96B(对应96 / 8 - 1 = 11号链表),先检查pool时候有余量,由于pool为空,此时调用malloc分配一大块作为pool,总共大小为96x 20 x 2 + RoundUp(1280 >> 4) = 3840 + 80 = 3920B,其中20个区块拿出来用,1个区块返回给容器,另外19个区块挂在#11链表上,剩下的就是pool的容量,那么pool为多大呢?3920−(96×20)=3920−1920=2000 B。另外累计申请量多大呢?上次是1280,这次申请的总共是3920, 加起来共5200.,单位都是bytes。如下图所示。

在这里插入图片描述

再次新建容器,里面元素大小是88B,现在是挂在几号链表呢? 88 / 8 - 1 = 10号链表。现在要看pool中的余量为2000,将2000切分为20个区块(每个区块88B),第一个区块给容器,剩下的19个区块挂在#10链表上。pool剩下的余量:2000 - 88 x 20 = 240B

在这里插入图片描述

25 G2.9 std::alloc运行一瞥06-10

下面这张图表示容器连续申请3次88B大小的空间,由于上面已经在#10 链表上挂了19个大小为88B的区块,这时候直接从该链表上拿下来3个区块返回给容器即可。pool大小还是240没变。

在这里插入图片描述

又来新的容器,它的元素大小为8B(该挂在几号链表呢?8 / 8 - 1 = 0号链表),上面的pool容量为240,从pool里面切分出20个区块,共8B x 20 = 160B大小,第一个返回给容器,其余19个区块挂在#0链表上。pool剩余多少呢?240 - 160 = 80B。

在这里插入图片描述

又来新的容器,它的元素大小为104B,该挂在几号链表呢?104 / 8 - 1 = 12号链表。但是pool余量为80B,一个大小为104B的区块都切分不了。此时这个80B大小的空间就是碎片,需要将其挂在某一个链表上,该挂在几号呢?80 / 8 - 1 = 9号链表。碎片处理完之后, 再来应付现在的需求:104B的分配。

现在再调用malloc分配一大块,大小为104 x 20 x 2 + RoundUp(5200 >> 4) = 4160 + RoundUp(325) = 4160 + 328 = 4488,

pool容量为4488 - 104 x 20 = 2408B

把第一个区块分配给容器,切出的19个挂在#12 list上。pool的余量为2408B。累计申请量为5200 + 4488 = 9688B

在这里插入图片描述

现在申请112B(挂在112 / 8 - 1 = 13号链表上),上面pool容量为2408,从里面取出112 * 20 = 2240,第一个区块返回给容器,留下19个区块挂在13号链表上。

pool余量为2408 - 2240 = 168B。

在这里插入图片描述

现在的新需求是申请48B(48 / 8 - 1 = 5号链表),上面的pool余量是168B,可以分配几个区块呢? 168 / 48 = 3 … 24, 所以可以分配3个大小为48的区块,pool余量为24B。

3个区块中第一个返回给容器,后面两个挂在5号链表上。

在这里插入图片描述

26 G2.9 std::alloc运行一瞥11-13

下面看一下内存分配失败的动作

新的容器请求72B的大小,挂在几号链表呢?72 / 8 - 1 = 8号链表,pool容量为24B,不足以分配一个大小为72B的区块,于是这个大小为24B的pool就成为碎片,需要挂在某一个链表上,24 / 8 - 1 = 2号链表,所以把这个pool余额挂在list #2, 然后调用malloc分配一大块:

72 x 20 x 2 + RoundUP(9688 >> 4) = 3488B , 在实验过程中把系统heap大小设置为10000,前面已经累计分配了9688B,因此无法满足本次内存分配。

alloc从手中资源取最接近72B的大小回填pool,这里最接近的是80B(9号链表有1个空的可用),然后从80B中切72B给容器,pool剩下80 - 72 = 8B。

在这里插入图片描述

容器再申请72B,list #8没有区块可用,pool余量为8,不足以供应1个,先将pool余额挂在list #0上,然后想要调用malloc分配72 x 20 x 2 + RoundUP(9688 >> 4) = 3488B的空间,依然无法满足分配。

于是alloc从手中资源取最接近72B的88B(list #10)回填pool,因为上面的80B(list #9)已经用完了,从88B中切出72B返回给容器,pool余额:88 - 72 = 16B。

在这里插入图片描述

新的容器申请120B,应该挂在几号链表呢?120 / 8 - 1 = 14号链表,同样的,pool供应不足,先将上面的16B挂在1号链表上,再想要malloc分配一大块也分配不出,无法满足需求。

于是,alloc从手中资源中取最接近120B的区块回填pool,但是14号链表和15号链表都是空的,于是无法分配。

在这里插入图片描述

检讨

但是system heap大小为10000,累计分配的总共为9688,还剩下312B可用,这部分内存不可以分配了吗?

另外一个问题,上图中白色区块都是可用资源,能不能将小区块合并为大区块分配给客户呢?合并技术难度极高。

在这里插入图片描述

27 G2.9 std::alloc源码剖析(上)

GNU C++2.9 分配器的设计:分为两级分配器,分别是第一级分配器和第二级分配器,其中第一级分配器不重要,主要模拟new handler的作用,处理一下内存分配的情况。下图便是第一级分配器的代码。

第一级分配器的代码1,侯捷老师没讲。

在这里插入图片描述

第一级分配器的代码2,侯捷老师没讲。

在这里插入图片描述

第一级分配器的代码3,侯捷老师没讲。

在这里插入图片描述

现在是第二级分配器的介绍

ROUND_UP是将分配的大小变成8的倍数

size_t ROUND_UP(size_t bytes) {return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}

FREELIST_INDEX的作用

size_t FREELIST_INDEX(size_t bytes) {return (((bytes) + __ALIGN-1)/__ALIGN - 1);
}

这个函数的作用是根据bytes大小,指定到是哪个链表。不同的bytes分配到上面16个链表之一。

下面几个变量的作用:

char*   start_free = 0; // 指向战备池pool的头
char*   end_free = 0;   // 指向战备池pool的尾
size_t  heap_size = 0;  // 累计分配大小

在这里插入图片描述

重要的两个函数:allocate和deallocate

在这里插入图片描述

allocate

void* allocate(size_t n)  //n must be > 0
{obj* volatile *my_free_list;    //obj** my_free_list;obj* result;if (n > (size_t)__MAX_BYTES) { // 大于128B,交给第一级分配器来分配return(malloc_allocate(n));}my_free_list = free_list + FREELIST_INDEX(n);  // 定位到几号链表result = *my_free_list;if (result == 0) {  // 该链表为空void* r = refill(ROUND_UP(n));  // 链表充值,从pool中去拿区块,链表有了可用区块return r;}// 指针指向下一个可用区块*my_free_list = result->free_list_link;return (result);
}

辅助理解allocate的图:

在这里插入图片描述

deallocate:将指针p指向的空间回收到单向链表中

下面的free_list_link就是next指针

void deallocate(void *p, size_t n)  //p may not be 0
{obj* q = (obj*)p;obj* volatile *my_free_list;   //obj** my_free_list;if (n > (size_t) __MAX_BYTES) { // 大于128B,改用第一级分配器进行回收malloc_deallocate(p, n);return;}// 回收过来的p指针指向的区块,挂在单向链表的头my_free_list = free_list + FREELIST_INDEX(n); // 指向16个链表中的一个,比如list #6,如下图所示q->free_list_link = *my_free_list; // q的next指向单向链表的头,就是list #6指向的具体区块的单向链表*my_free_list = q;  // my_free_list重新指向新的单向链表q,因为头指针被换成新的了
}

辅助理解deallocate的图:

在这里插入图片描述

28 G2.9 std::alloc源码剖析(中)

chunk_alloc函数 主要作用是分配一大块内存。

做法:要先看战备池pool,看可以分配多少个小区块,如果存在碎片也要处理。如果pool是空的,还需要调用malloc给pool分配内存。

下面是具体的逻辑

第一张ppt展示3大块逻辑(if else):

  1. 首先看pool空间满足20个区块的需求,
  2. 其次是看pool空间只能满足1到19个区块的需求,
  3. 最后是pool不能满足1块(pool碎片,或者pool空间为0).

在这里插入图片描述

下面的ppt展示的是对3的处理:具体调用malloc分配内存,或者是往更大区块的链表请求支援(分配一个区块)。

在这里插入图片描述

chunk_alloc的源代码:给予了清晰的注解(根据侯捷老师的讲解)

//----------------------------------------------
// We allocate memory in large chunks in order to
// avoid fragmentingthe malloc heap too much.
// We assume that size is properly aligned.
//
// Allocates a chunk for nobjs of size "size".
// nobjs may be reduced if it is inconvenient to
// allocate the requested number.
//----------------------------------------------
//char* chunk_alloc(size_t size, int& nobjs)  //G291[o],VC6[x],CB5[x]
char* chunk_alloc(size_t size, int* nobjs)
{char* result;// 调用chunk_alloc的时候,nobjs为20,所以默认total_bytes为20个区块的大小size_t total_bytes = size * (*nobjs);   //原 nobjs 改為 (*nobjs)size_t bytes_left = end_free - start_free; // pool中剩余的字节个数if (bytes_left >= total_bytes) {  // 1. pool空间足以满足20块需求result = start_free;start_free += total_bytes;  // 调整pool水位,下降,战备池pool变小return(result);} else if (bytes_left >= size) { // 2. pool空间只能满足1个区块的需求,但不足20块*nobjs = bytes_left / size;         //原 nobjs 改為 (*nobjs) // 改变需求数,看看可以切成几个区块total_bytes = size * (*nobjs);      //原 nobjs 改為 (*nobjs)  // 改变需求总量result = start_free;start_free += total_bytes;return(result);} else {  // 3. pool空间不足以满足1块需求,pool空间可能是碎片,也可能表示pool大小为0size_t bytes_to_get =  // 需要请求的一大块的大小2 * total_bytes + ROUND_UP(heap_size >> 4);// Try to make use of the left-over piece.// pool池的碎片,挂到对应大小的链表上if (bytes_left > 0) {obj* volatile *my_free_list =  // 找出应转移到第#号链表free_list + FREELIST_INDEX(bytes_left);// 将pool空间编入第#号链表,next指针指向链表的头节点,编入链表的第一个节点((obj*)start_free)->free_list_link = *my_free_list;*my_free_list = (obj*)start_free;}// 上面处理完pool的碎片,pool为空,开始用malloc为pool分配内存start_free = (char*)malloc(bytes_to_get); // pool的起点if (0 == start_free) {  // 分配失败,从free list中找区块(向右边更大的区块去找)int i;obj* volatile *my_free_list, *p;//Try to make do with what we have. That can't//hurt. We do not try smaller requests, since that tends//to result in disaster on multi-process machines.for (i = size; i <= __MAX_BYTES; i += __ALIGN) { // 向右侧的链表去找区块,例如88B,96B,104B, 112B等等my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;if (0 != p) { // 找到右边的list中的可用区块,只释放一块给pool*my_free_list = p -> free_list_link;// start_free 和 end_free是战备池pool的头尾指针,指向这一块空间start_free = (char*)p;  end_free = start_free + i;return(chunk_alloc(size, nobjs)); // 递归再试一次//Any leftover piece will eventually make it to the//right free list.}}end_free = 0;       //In case of exception.start_free = (char*)malloc_allocate(bytes_to_get);//This should either throw an exception or//remedy the situation. Thus we assume it//succeeded.} // if结束heap_size += bytes_to_get;  // 累计总分配量end_free = start_free + bytes_to_get;  // 调整pool水位,pool空间变大,pool的终点return(chunk_alloc(size, nobjs));  //递归再调用一次}
}

refill函数

当第n条链表来服务时,却发现其为空,此时就调用refill函数来创建链表,分配区块。

下面的int nobjs = 20;表示默认分配20个区块,可能比20小。调用chunk_alloc函数来挖一大块内存。

当分配的区块为1时,就直接交给客户(我们上文用的容器)。

chunk指的是一大块,然后将其切割,建立free list。refill的参数n表示一小个区块的大小。

refill是充值,在一大块内存中切割成nobjs个区块,构成链表。

在这里插入图片描述

//----------------------------------------------
// Returns an object of size n, and optionally adds
// to size n free list. We assume that n is properly aligned.
// We hold the allocation lock.
//----------------------------------------------
void* refill(size_t n)
{int nobjs = 20;char* chunk = chunk_alloc(n,&nobjs);obj* volatile *my_free_list;   //obj** my_free_list;obj* result;obj* current_obj;obj* next_obj;int i;if (1 == nobjs) return(chunk);my_free_list = free_list + FREELIST_INDEX(n);//Build free list in chunkresult = (obj*)chunk;*my_free_list = next_obj = (obj*)(chunk + n);for (i=1;  ; ++i) {current_obj = next_obj;next_obj = (obj*)((char*)next_obj + n);if (nobjs-1 == i) {current_obj->free_list_link = 0;break;} else {current_obj->free_list_link = next_obj;}}return(result);
}

在这段代码中,next_obj 的计算是为了在内存块(chunk)上构建一个链表,以形成一个自由链表(free list)。这个链表将被用于分配对象。让我们解释一下这行代码的目的:

next_obj = (obj*)((char*)next_obj + n);
  • next_obj 是一个指向当前空闲块的指针,它一开始指向 chunk + n,即第一个可用的内存块。
  • (char*)next_objnext_obj 强制类型转换为 char* 类型,这是因为我们希望按字节递增,而不是按对象递增。
  • ((char*)next_obj + n) 表示将指针移动到下一个内存块的起始位置,即当前内存块的末尾加上一个对象的大小 n
  • (obj*)((char*)next_obj + n) 将移动后的指针重新转换为 obj* 类型,以便正确指向下一个空闲块的起始位置。

这样,通过不断地按对象大小 n 的步长在内存块上移动,构建了一个包含多个空闲块的链表。这个链表可以有效地用于分配对象。这种处理方式是为了确保链表中相邻的空闲块之间的间隔是 n 字节,从而满足对象的对齐需求。

在上述代码中,for 循环的第二个条件 ; 是一个空语句,表示没有额外的条件来控制循环的执行。这意味着 for 循环会一直执行,直到执行到 break 语句为止。

在这个具体的代码中,循环的目的是为了在内存块上构建一个链表,将多个空闲块连接在一起。循环体中的 if 语句用于判断是否已经遍历了 nobjs-1 个空闲块。如果是,则最后一个空闲块的 free_list_link 设置为 0,表示链表的结束。此时,break 语句被执行,跳出循环。

因此,循环终止的条件是遍历了 nobjs-1 个空闲块,确保链表的正确构建,并在最后一个空闲块处设置了结束标志。循环的终止是由 break 语句触发的,而不是由循环条件控制的。

29 G2.9 std::alloc源码剖析(下)

分析chunk_alloc函数,本笔记将其放到了28 G2.9 std::alloc源码剖析(中)进行。

具体一些数据的初始定义

在这里插入图片描述

30 G2.9 std::alloc观念大整理

alloc观念大整理

list<Foo> c;
c.push_back(Foo(1)); // Foo(1)临时对象,创建在栈stack中
//容器c使用alloc分配空间,看16条链表中哪一条可以提供区块,分配给它。所以它不带cookie
Foo* p = new Foo(2); // 采用new,创建在heap中
// new是调用operator new,底层是调用malloc,它带有cookie
c.push_back(*p); // 容器c push_back的时候不带cookie
delete p; 

在这里插入图片描述

GNU C++2.9的alloc批斗大会

需要学习的地方

比较判断的时候把具体的值写在前面,防止出现将==写成=赋值的情况。

if(0 == start_free)
if(0 != p)
if(1 == nobjs)        

不好的地方

变量定义的地方不要和使用的地方间隔太远,尤其是指针的使用。

第一级分配器使用的一个函数:炫技,没有人看得懂

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

等价于

typedef void (*H)();
H set_malloc_handler(H f);

还有deallocate的时候并没有free掉,这是由设计的先天缺陷造成的。

在这里插入图片描述

31 G4.9 pull allocator运行观察

在GNU C++4.9版本下的测试,由于2.9版本分配内存都是调用malloc,无法重载,即无法接管到我们用户手中,无法记录总分配量和总释放量。而在4.9版本中,它的内存分配动作是调用operator new,这样我们就可以接管operator new,对其进行重载。

在这里插入图片描述

下面测试两个分配器使用情况,分别对list容器进行100万次分配。

由于GNU C++4.9版本标准分配器是allocator,并不是2.9版本alloc,所以容器(客户)分配内存的时候每个元素(100万个)都带cookie(每个cookie占8B)。这个如下图右侧所示。

下图左侧使用4.9版本好的分配器__pool_alloc,这个是2.9版本的alloc,显示分配的次数timesNew为122次(调用malloc的次数),这比右侧标准分配器好上不少。

在这里插入图片描述

后记

截至2024年1月11日19点39分,完成《C++内存管理》第二讲allocator的学习,主要涉及GNU C++2.9版本alloc分配器的具体实现与源码剖析。功力提升不少,希望更进一步。

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

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

相关文章

11 双向链表

单链表的局限&#xff1a; 单链表的缺点&#xff1a;逆序访问单链表中的元素耗时大。&#xff08;时间复杂度&#xff1a;O&#xff09; 双向链表的定义 第0个节点【a1】的pre指针为NULL&#xff0c;要注意 插入操作&#xff1a; 删除操作&#xff1a; 初步实现双链表 代码&…

【Vue系列】Vue3快速构建项目,以及在已有代码情况首次打开如何初始化依赖项

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是是《前端》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌…

一天一个设计模式---适配器模式

概念 适配器模式是一种结构型设计模式&#xff0c;用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容的接口之间进行协同工作&#xff0c;使得原本由于接口不匹配而无法合作的类能够一起工作。 具体内容 适配器模式主要包括以下几个要素&#xff1a; 目标接…

VBA中类的解读及应用第八讲:实现定时器功能的自定义类事件

《VBA中类的解读及应用》教程【10165646】是我推出的第五套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。 类&#xff0c;是非常抽象的&#xff0c;更具研究的价值。随着我们学习、应用VBA的深入&#xff0…

Java中输入和输出处理(三)二进制篇

叮咚&#xff01;加油&#xff01;马上学完 读写二进制文件Data DataInputStream类 FilFeInputStream的子类 与FileInputStream类结合使用读取二进制文件 DataOutputStream类 FileOutputStream的子类 与FileOutputStream类结合使用写二进制文件 读写二进制代码 package 面…

vue实现-年、月、日、时、分、秒、星期?

一、文章引导 #mermaid-svg-nP4oT3Y4d6oaxUsg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-nP4oT3Y4d6oaxUsg .error-icon{fill:#552222;}#mermaid-svg-nP4oT3Y4d6oaxUsg .error-text{fill:#552222;stroke:#55222…

倍福(Bechhoff) CX8090嵌入式PC控制器开发没有想像中的那么难

笔者曾2023年初曾为云南阜外医院新风系统开发自动控制系统。医院所有新风设备和公区照明全部采用倍福嵌入式PC控制器实现智能控制。其中新风和供水计量通过CX8090实现控制&#xff1b;公区照明通过BC9050实现控制&#xff1b;并采用美国邦纳人机界面(THM035B&#xff09;实现远…

DB2除法的小数位问题(四舍五入问题)以及其他常用的函数

DB2除法的小数位问题&#xff08;四舍五入问题&#xff09;以及其他常用的函数 1. DB2取第一条数据2. DB2 中指定值排序2.1 使用case when2.2 使用decode函数 3. 拼接函数4. 强制转换类型——cast函数5. DB2除法的小数位问题&#xff08;四舍五入问题&#xff09;5.1 关于round…

three.js 关键帧动画

效果&#xff1a; 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div class"box-right"…

金和OA jc6 GetAttOut SQL注入漏洞复现

0x01 产品简介 金和OA协同办公管理系统软件(简称金和OA),本着简单、适用、高效的原则,贴合企事业单位的实际需求,实行通用化、标准化、智能化、人性化的产品设计,充分体现企事业单位规范管理、提高办公效率的核心思想,为用户提供一整套标准的办公自动化解决方案,以帮助…

语义解析:如何基于SQL去实现自然语言与机器智能连接的桥梁

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 语义解析 定义 作用 语义解析的应用场景 场景一&#xff1a; 场景二&#xff1a; 总结语…

【LeetCode】winter vacation training

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb; 有效的字母异位词&#x…

129基于matlab的粒子群算法、遗传算法、鲸鱼算法、改进鲸鱼算法优化最小二乘支持向量机(lssvm)的gam正则化参数和sig2RBF函数的参数

基于matlab的粒子群算法、遗传算法、鲸鱼算法、改进鲸鱼算法优化最小二乘支持向量机&#xff08;lssvm&#xff09;的gam正则化参数和sig2RBF函数的参数。输出适应度曲线&#xff0c;测试机和训练集准确率。程序已调通&#xff0c;可直接运行。 129 matlabLSSVM优化算法 (xiaoh…

【排序】对各种排序的总结

文章目录 前言1. 排序算法的复杂度及稳定性分析2. 排序算法的性能测试2.1 重复率较低的随机值排序测试2.2 重复率较高的随机值排序测试 前言 本篇是基于我这几篇博客做的一个总结&#xff1a; 《简单排序》&#xff08;含&#xff1a;冒泡排序&#xff0c;直接插入排序&#x…

RabbitMQ(六)消息的持久化

目录 一、简介1.1 定义1.2 消息丢失的场景 二、交换机的持久化方式一&#xff1a;直接 new方式二&#xff1a;channel.exchangeDeclare()方式三&#xff1a;ExchangeBuilder【推荐】 三、队列的持久化方式一&#xff1a;直接 new方式二&#xff1a;channel.queueDeclare()方式三…

怎么样检查自己系统上的Python环境中是否有某个包(扩展库)?

比如我们这里想看下有没有库pytz 很简单&#xff0c;进入Python的命令行&#xff0c;然后输入下面的命令&#xff1a; import pytz如果有这个库&#xff0c;则不会报错&#xff0c;否则会报错。 Windows的测试结果如下&#xff1a; Centos的测试结果如下&#xff1a;

Hive分区表实战 - 多分区字段

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;创建学校数据库&#xff08;二&#xff09;创建省市分区的大学表&#xff08;三&#xff09;在本地创建数据文件1、创建四川成都学校数据文件2、创建四川泸州学校数据文件3、创建江苏南京学校数据文件4、创建江苏苏…

字节8年经验之谈!一文从0到1带你入门接口测试【建议收藏】

扫盲内容&#xff1a; 1.为什么要做接口测试&#xff1f; 2.怎样做接口测试&#xff1f; 3.接口测测试点是什么&#xff1f; 4.接口测试都要掌握哪些知识&#xff1f; 5.其他相关知识&#xff1f; 一.为什么要做接口测试&#xff1f; ①.越底层发现bug&#xff0c;它的修…

5G之味,在烟火长沙

今年夏天&#xff0c;有一部电影叫做《长沙夜生活》。影片讲述了长沙大排档中的一些故事。网红大排档的老板娘、厨师、顾客&#xff0c;他们的邂逅、热爱、留下、离开、和解、团圆&#xff0c;都发生在一段夜色里&#xff0c;发生在充满烟火气的长沙城。 有没有想过这样一个问题…

线性表入门

王有志&#xff0c;一个分享硬核Java技术的互金摸鱼侠加入Java人的提桶跑路群&#xff1a;共同富裕的Java人 从今天开始就进入到数据结构的部分了&#xff0c;整体分为3个部分&#xff1a;线性表&#xff0c;树和图&#xff0c;从认识每种数据结构到它们的高级应用。今天我们先…