【池化技术】内存池技术原理和C语言实现

64 篇文章 94 订阅

一、基础概念

在讲内存池技术之前,咱们先对一些基础概念进行阐述,以便能更好的讨论问题。

1.一个可执行程序占用的内存分为哪几个区?一个进程的虚拟内存区域有哪些?

C语言在内存中一共分为如下几个区域,分别是:

  1. 内存栈区(stack): 存放局部变量名;

  2. 内存堆区(heap): 存放new或者malloc出来的对象;

  3. 常数区: 存放局部变量或者全局变量的值;

  4. 静态区: 用于存放全局变量或者静态变量;

  5. 代码区:二进制代码。

img

2.静态内存分配和动态内存分配

一个程序被加载到内存中,这块内存首先就存在两种属性:静态分配内存和动态分配内存。
静态分配内存:是在程序编译和链接时就确定好的内存。
动态分配内存:是在程序加载、调入、执行的时候分配/回收的内存。

二、malloc实现原理

在了解内存池技术之前,我们先对一个我们在C语言中经常使用的动态内存分配函数malloc进行深入了解。

void *malloc(size_t size)
  • size – 内存块的大小,以字节为单位。
  • 该函数返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL。

动态内存分配器更方便,也有更好的移植性。它维护者一个进程的虚拟内存区域,称为堆(heap)。它在未初始化数据的上边,然后向上生长(即向更高的地址),对于每个进程来说,内核维护着一个brk(break)变量,它指向堆的顶部,如下图:

img

分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行。(忽然很庆幸自己学习了一波多级页表,所以对里面这个模型比较容易理解 可参考 何柄融:linux 多级页表 cpu取虚拟地址 TLB 特别是里面的 chunk )

然后动态的分配器有两种基本风格:显式和隐式。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已经分配的块。

显式分配器:要求应用显式地释放任何已经分配的块。例如c标准库中的malloc. c程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。c++中的new和delete操作符和c中搞得malloc和free相当。(就是自己手动释放内存

隐式分配器:要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做**垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃圾收集。例如java**,ML,Lisp之类的高级语言就依赖垃圾收集来释放已分配的块。(就是不用程序猿自己手动释放内存

然后来深入了解malloc:

先了解:brk()和sbrk()函数

int brk( const void *addr )
void* sbrk ( intptr_t incr );

这两个函数的作用主要是扩展heap的上界brk。第一个函数的参数为设置的新的brk上界地址,如果成功返回0,失败返回-1。第二个函数的参数为需要申请的内存的大小,然后返回heap新的上界brk地址。如果sbrk的参数为0,则返回的为原来的brk地址。

然后来了解:mmap

mmap函数第一种用法是映射磁盘文件到内存中(前面讲进程通信的时候讲过);而malloc使用的mmap函数的第二种用法,即匿名映射,匿名映射不映射磁盘文件,而是向映射区申请一块内存

void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);

这里不重复啰嗦了,对mmap不熟悉的同学可以参考何柄融:linux 进程通信方式 pipe无名管道 fifo有名管道 共享内存映射 socket 消息队列 或者文章最前面的连接(感觉我写得比较好,哈哈)

这里要注意的是fd参数,fd为映射的文件描述符,如果是匿名映射,可以设为-1

当申请小内存的时,malloc使用sbrk分配内存;当申请大内存时,使用mmap函数申请内存;但是这只是分配了虚拟内存,还没有映射到物理内存,当访问申请的内存时,才会因为缺页异常,内核分配物理内存

然后接着深入:

由于brk/sbrk/mmap属于系统调用,如果每次申请内存,都调用这三个函数中的一个,那么每次都要产生系统调用开销(即cpu从用户态切换到内核态的上下文切换,这里要保存用户态数据,等会还要切换回用户态),这是非常影响性能的;其次,这样申请的内存容易产生碎片,因为堆是从低地址到高地址,如果低地址的内存没有被释放,高地址的内存就不能被回收。

鉴于此,malloc采用的是内存池的实现方式,malloc内存池实现方式更类似于STL分配器和memcached的内存池,先申请一大块内存,然后将内存分成不同大小的内存块,然后用户申请内存时,直接从内存池中选择一块相近的内存块即可

由于文首的链接里没图,所以从 glibc内存管理那些事儿 这个链接里盗来几张图,而且这个链接讲的内存池也很不错。

先看内存池的整体结构:(感觉下面的都很核心!)

img(第一眼看上去有没有很像哈希表?哈哈。)

malloc将内存分成了大小不同的chunk,然后通过bins来组织起来。malloc将相似大小的chunk(图中可以看出同一链表上的chunk大小差不多)用双向链表链接起来,这样一个链表被称为一个bin。malloc一共维护了128个bin,并使用一个数组来存储这些bin。数组中第一个为unsorted bin,数组从2开始编号,前64个bin为small bins,同一个small bin中的chunk具有相同的大小,两个相邻的small bin中的chunk大小相差8bytes。small bins后面的bin被称作large bins。large bins中的每一个bin分别包含了一个给定范围内的chunk,其中的chunk按大小序排列。large bin的每个bin相差64字节。(这里我本来想计算一下加深理解的,8x64=512b, 64x64=4096b 然后加起来发现和图片里的不太匹配,所以不知道自己哪里理解错了?可是前面的88又对啊,怎么后面就12k那么大呢?)

malloc除了有unsorted bin,small bin,large bin三个bin之外,还有一个fast bin。一般的情况是,程序在运行时会经常需要申请和释放一些较小的内存空间。当分配器合并了相邻的几个小的 chunk 之后,也许马上就会有另一个小块内存的请求,这样分配器又需要从大的空闲内存中切分出一块,这样无疑是比较低效的,故而,malloc 中在分配过程中引入了 fast bins,不大于 max_fast(默认值为 64B)的 chunk 被释放后,首先会被放到 fast bins中,fast bins 中的 chunk 并不改变它的使用标志 P。这样也就无法将它们合并,当需要给用户分配的 chunk 小于或等于 max_fast 时,malloc 首先会在 fast bins 中查找相应的空闲块,然后才会去查找 bins 中的空闲 chunk。在某个特定的时候,malloc 会遍历 fast bins 中的 chunk,将相邻的空闲 chunk 进行合并,并将合并后的 chunk 加入 unsorted bin 中,然后再将 usorted bin 里的 chunk 加入 bins 中

unsorted bin 的队列使用 bins 数组的第一个,如果被用户释放的 chunk 大于 max_fast,或者 fast bins 中的空闲 chunk 合并后,这些 chunk 首先会被放到 unsorted bin 队列中,在进行 malloc 操作的时候,如果在 fast bins 中没有找到合适的 chunk,则malloc 会先在 unsorted bin 中查找合适的空闲 chunk,然后才查找 bins。如果 unsorted bin 不能满足分配要求。 malloc便会将 unsorted bin 中的 chunk 加入 bins 中。然后再从 bins 中继续进行查找和分配过程。从这个过程可以看出来,unsorted bin 可以看做是 bins 的一个缓冲区,增加它只是为了加快分配的速度。(其实感觉在这里还利用了局部性原理,常用的内存块大小差不多,从unsorted bin这里取就行了,这个和TLB之类的都是异曲同工之妙啊!)

除了上述四种bins之外,malloc还有三种内存区。

  1. 当fast bin和bins都不能满足内存需求时,malloc会设法在top chunk中分配一块内存给用户;top chunk为在mmap区域分配一块较大的空闲内存模拟sub-heap。(比较大的时候)
  2. 当chunk足够大,fast bin和bins都不能满足要求,甚至top chunk都不能满足时,malloc会从mmap来直接使用内存映射来将页映射到进程空间,这样的chunk释放时,直接解除映射,归还给操作系统。(极限大的时候)
  3. Last remainder是另外一种特殊的chunk,就像top chunk和mmaped chunk一样,不会在任何bins中找到这种chunk。当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需要的small chunk大小,last remainder chunk被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chunk。(这个应该是fast bins中也找不到合适的时候,用于极限小的)

malloc内存分配(下面算是正常一般的情况了)

一开始时,brk和start_brk是相等的,这时实际heap大小为0;如果第一次用户请求的内存大小小于mmap分配阈值,则malloc会申请(chunk_size+128kb) align 4kb大小的空间作为初始的heap。初始化heap之后,第二次申请的内存如果还是小于mmap分配阈值时,malloc会先查找fast bins,如果不能找到匹配的chunk,则查找small bins。若还是不行,合并fast bins,把chunk 加入到unsorted bin,在unsorted bin中查找,若还是不行,把unsorted bin中的chunk全加入large bins中,并查找large bins。在fast bins和small bins中查找都需要精确匹配,而在large bins中查找时,则遵循"smalest-first,best-fit"的原则,不需要精确匹配。

若以上都失败了,malloc则会考虑使用top chunk。若top chunk也不能满足分配,且所需的chunk大小大于mmap分配阈值,则使用mmap进行分配。否则增加heap,增加top chunk,以满足分配要求。

现在来不上chunk的结构图:

malloc利用chunk结构来管理内存块,malloc就是由不同大小的chunk链表组成的。一个使用中的chunk的结构如下图:

img

malloc会给用户分配的空间的前后加上一些控制信息,用这样的方法来记录分配的信息,以便完成分配和释放工作。chunk指针指向chunk开始的地方,图中的mem指针才是真正返回给用户的内存指针。

  1. chunk 的第二个域的最低一位为 P,它表示前一个块是否在使用中,P 为 0 则表示前一个 chunk 为空闲,这时chunk的第一个域 prev_size 才有效,prev_size 表示前一个 chunk 的 size,程序可以使用这个值来找到前一个 chunk 的开始地址。当 P 为 1 时,表示前一个 chunk 正在使用中,prev_size程序也就不可以得到前一个 chunk 的大小。不能对前一个 chunk 进行任何操作malloc分配的第一个块总是将 P 设为 1,以防止程序引用到不存在的区域。(这里就很细!)
  2. Chunk 的第二个域的倒数第二个位为 M,他表示当前 chunk 是从哪个内存区域获得的虚拟内存M 为 1 表示该 chunk 是从 mmap 映射区域分配的,否则是从 heap 区域分配的
  3. Chunk 的第二个域倒数第三个位为 A,表示该 chunk 属于主分配区或者非主分配区,如果属于非主分配区,将该位置为 1,否则置为 0。

右边图是空闲的chunk:

当chunk空闲时,其M状态是不存在的,只有AP状态,原本是用户数据区的地方存储了四个指针,指针fd指向后一个空闲的chunk,而bk指向前一个空闲的chunk,malloc通过这两个指针将大小相近的chunk连成一个双向链表。在large bin中的空闲chunk,还有两个指针,fd_nextsize和bk_nextsize,用于加快在large bin中查找最近匹配的空闲chunk。不同的chunk链表又是通过bins或者fastbins来组织的。(这里就很符合网上大多数人说的链表理论了)

以上内容大部分参考阿里华庭写的Glibc内存管理,Ptmalloc2源代码分析。

感觉从这个人这里学到了很多东西,是个大佬。

然后结合网上的一般回答:

链接:https://www.nowcoder.com/questionTerminal/5aae63b290c542f0ab0582d293e6c791
来源:牛客网

malloc可以分别由伙伴系统或基于链表的实现;

1、它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表;

2、 调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。

3、 调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。

虽然这个答案比较粗糙,但是也有大概的思想了。

malloc和free的实现原理 这位大哥讲得也不错,可以参考一下。

最后可以结合 腾讯云技术社区:十问 Linux 虚拟内存管理 (glibc) (一) 腾讯的这个实践来理论联系实践。下面参考于这个腾讯的链接(建议可以去点个赞):

malloc 是 glibc 中内存分配函数,也是最常用的动态内存分配函数,其内存必须通过 free 进行释放,否则导致内存泄露。

关于 malloc 获得虚存空间的实现,与 glibc 的版本有关,但大体逻辑是:

  1. 若分配内存小于 128k ,调用 sbrk() ,将堆顶指针向高地址移动,获得新的虚存空间。
  2. 若分配内存大于 128k ,调用 mmap() ,在文件映射区域中分配匿名虚存空间。
  3. 这里讨论的是简单情况,如果涉及并发可能会复杂一些,不过先不讨论。

其中 sbrk 就是修改栈顶指针位置,而 mmap 可用于生成文件的映射以及匿名页面的内存,这里指的是匿名页面。而这个 128k ,是 glibc 的默认配置,可通过函数 mallopt 来设置

接着: VSZ为虚拟内存 RSS为物理内存

  1. VSZ 并不是每次 malloc 后都增长,是与上一节说的堆顶没发生变化有关,因为可重用堆顶内剩余的空间,这样的 malloc 是很轻量快速的。
  2. 但如果 VSZ 发生变化,基本与分配内存量相当,因为 VSZ 是计算虚拟地址空间总大小。
  3. RSS 的增量很少,是因为 malloc 分配的内存并不就马上分配实际存储空间,只有第一次使用,如第一次 memset 后才会分配。
  4. 由于每个物理内存页面大小是 4k ,不管 memset 其中的 1k 还是 5k 、 7k ,实际占用物理内存总是 4k 的倍数。所以 RSS 的增量总是 4k 的倍数
  5. 因此,不是 malloc 后就马上占用实际内存,而是第一次使用时发现虚存对应的物理页面未分配,产生缺页中断,才真正分配物理页面,同时更新进程页面的映射关系。这也是 Linux 虚拟内存管理的核心概念之一

三、使用C语言实现一个内存池

在了解了内存池技术的原理之后,我们就可以自己来实现一个内存池。

1.C语言实现一个内存池

参考这篇文章:用C语言实现的简易内存池

pool.h

#ifndef _POOL_H
#define _POOL_H
 
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
 
#define LEN sizeof(memblock)//内存块节点大小
#define OPERA_OK  0//操作失败
#define OPERA_ERR 1//操作成功
//内存块节点
typedef struct MEMBLOCK
{
	char* pmem;				//内存指针 
	MEMBLOCK *next;		//指向下一节点的指针
}memblock;
//内存池节点
typedef struct MEMPOOL
{
	int  cnt;								//数量
	int  usedcnt;						//使用个数
	int blocksize;						//内存块大小
	char* firstaddr;					//起始地址
	char* lastaddr;						//结束地址
	MEMBLOCK *firstblock;		//指向下一节点的指针
}mempool;
 
/********************************************
*@brief 创建n个节点的内存池链表
*@param
*    num			数量
*    blocksize   内存块大小
*@return OPERA_OK/OPERA_ERR
*@see
*********************************************/
mempool *CreatePool(int num, int blocksize);
 
/********************************************
*@brief 销毁内存池
*@param
*    poolhead			内存池指针
*@return OPERA_OK/OPERA_ERR
*@see
*********************************************/
int  DestroyPool(mempool  *poolhead);
 
/********************************************
*@brief  得到一个内存块
*@param
*    poolhead			内存池指针
*@return 内存块地址
*@see
*********************************************/
char *GetMemblock(mempool *poolhead);
 
/********************************************
*@brief  释放一个内存块
*@param
*	 pmem				内存块地址
*    poolhead		内存池指针
*@return  OPERA_OK/OPERA_ERR
*@see
*********************************************/
int ReleaseMemblock(char* pmem, mempool *poolhead);
 
 
#endif

pool.c

#include "pool.h"
 
/********************************************
*@brief 创建n个节点的内存池链表
*@param
*    num			数量
*    blocksize   内存块大小
*@return OPERA_OK/OPERA_ERR
*@see
*********************************************/
 mempool *CreatePool(int num,int blocksize )
{
	if(num<=0||blocksize<=0)
	{
		printf("Create  input err\n");
		return NULL;
	}
	//内存池指针分配内存
	mempool *poolhead=NULL;		//内存池指针
	poolhead=(mempool*)malloc(sizeof(mempool));//池节点申请内存
	if (NULL==poolhead)
	{
		printf("poolhead malloc err!\n");
		return NULL;
	}
	memset(poolhead,0,sizeof(mempool));
	//内存池指针部分初始化赋值,其他默认为0无需初始
	poolhead->cnt=num;
	poolhead->blocksize=blocksize;
	//定义链表操作临时指针
	memblock *p1 = NULL;	//创建的新节点的地址
	memblock *p2 = NULL;	
	char* block;						//内存块指针
	int n = 0;							//创建前链表的节点总数为0:空链表
	//分配第一个内存块
	p1 = ( memblock *) malloc (LEN);	//开辟第一个新节点
	if (NULL == p1)
	{
		printf("p1 %d malloc err!\n",n+1);
		return NULL;
	}
	memset(p1,0,LEN);
	block=(char*)malloc(blocksize);//开辟内存块
	if (NULL == block)
	{
		printf("firstblock malloc err!\n");
		return NULL;
	}
	memset(block,0,blocksize);
	//*内存池,内存块初始化赋值
	p1->pmem=block;
	poolhead->firstaddr=block;
 	p2 = p1;			//如果节点开辟成功,则p2先把它的指针保存下来以备后用
	if(p1==NULL)		//节点开辟不成功
	{
		printf ("\nCann't create it, try it again in a moment!\n");
		return NULL;
	}
	while(n <num)		
	{
		n += 1;			//节点总数增加1个	
		if(n == 1)		//如果节点总数是1,则head指向刚创建的节点p1
		{
			poolhead->firstblock = p1;
			p2->next = NULL;  //此时的p2就是p1,也就是p1->next指向NULL。
		}
		else
		{
			p2->next = p1;	//指向上次下面刚刚开辟的新节点
		}
		p2 = p1;			//把p1的地址给p2保留,然后p1产生新的节点
 
		p1 = ( memblock *) malloc (LEN);//开辟出新节点
		if (NULL == p1)
		{
			printf("p1 %d malloc err!\n",n+1);
			return NULL;
		}
		memset(p1,0,LEN);
		block=(char*)malloc(blocksize);//开辟出新内存块
		if (NULL == block)
		{
			printf("block %d malloc err!\n", n + 1);
			return NULL;
		}
		memset(block,0,blocksize);
		p1->pmem=block;
	}
	p2->next = NULL;		//链表的最后一个节点指向NULL
	poolhead->lastaddr=p2->pmem+blocksize;//内存池 末尾地址
 
	free(p1->pmem);
	free(p1);			//跳出了while循环,释放p1   多余的那个空间
	p1 = NULL;			
	return poolhead;	    //返回创建链表的头指针 
}
 
 /********************************************
 *@brief 销毁内存池
 *@param
 *    poolhead			内存池指针
 *@return OPERA_OK/OPERA_ERR
 *@see
 *********************************************/
int  DestroyPool(mempool  *poolhead)  
{  
	if(poolhead==NULL)
	{
		return OPERA_ERR;
	}
	memblock  *p1=poolhead->firstblock;  
	memblock  *p2=p1;
    while(p1!=NULL)  
    {  
        p2=p1;  
		p1=p1->next;  
		free(p2->pmem);
		p2->pmem=NULL;
        free(p2);  
    }  
	poolhead->firstblock=NULL;	
	free(poolhead);
	poolhead = NULL;
	return OPERA_OK;  
} 
 
/********************************************
*@brief  得到一个内存块
*@param
*    poolhead			内存池指针
*@return 内存块地址
*@see
*********************************************/
char *GetMemblock(mempool *poolhead)
{
	if(poolhead->usedcnt==poolhead->cnt)
	{
		printf("GetMemblock ERR !Pool Full!\n");
		return NULL;
	}
	if (poolhead==NULL||poolhead->firstblock==NULL)
	{
		printf("pool in err!\n");
		return NULL;
	}
	memblock* p=poolhead->firstblock;
	poolhead->firstblock = p->next;
	p->next = NULL;
	poolhead->usedcnt++;
	return p->pmem;
}
/********************************************
*@brief  释放一个内存块
*@param
*	 pmem				内存块地址
*    poolhead			内存池指针
*@return  OPERA_OK/OPERA_ERR
*@see
*********************************************/
int ReleaseMemblock(char* pmem, mempool *poolhead)
{
	if(pmem==NULL)
	{
		printf("Realease Mem input ERR!\n");
		return OPERA_ERR;
	}
	memblock *ptemp = (memblock *)malloc(LEN);
	if (NULL == ptemp)
	{
		printf("ptemp malloc err!\n");
		return OPERA_ERR;
	}
	ptemp->pmem = pmem;
	ptemp->next=poolhead->firstblock;
	poolhead->firstblock = ptemp;
	poolhead->usedcnt--;
	return OPERA_OK;
}

test.c

#include "pool.h"
int main()
{
	int ret = OPERA_OK;
	mempool *pool = NULL;
	pool = CreatePool(3, 1024);//建立内存池
	if (pool == NULL)
	{
		printf("Creat ERR!\n");
	}
	else
	{
		printf("Creat OK!\n");
	}
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
	char *Mem1 = GetMemblock(pool);
	printf("得到内存块地址0x%p\n", Mem1);
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
	char *Mem2 = GetMemblock(pool);
	printf("得到内存块地址0x%p\n", Mem2);
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
	char *Mem3 = GetMemblock(pool);
	printf("得到内存块地址0x%p\n", Mem3);
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
	char *Mem4 = GetMemblock(pool);
	printf("得到内存块地址0x%p\n", Mem4);
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
 
 
	ReleaseMemblock(Mem2, pool);
	printf("释放第二个内存块0x%p\n", Mem2);
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
	char *Mem5 = GetMemblock(pool);
	printf("得到内存块地址0x%p\n", Mem5);
	printf("内存池信息 块数量%d,块大小%d,使用个数%d,起始地址0x%p,终止地址0x%p\n", pool->cnt, pool->blocksize, pool->usedcnt, pool->firstaddr, pool->lastaddr);
 
 
	ret = DestroyPool(pool);//销毁内存池
 
	if (ret == OPERA_ERR)
	{
		printf("Destory ERR!\n");
	}
	else
	{
		printf("Destroy OK!\n");
	}
	system("pause");
	return 0;
}

编译这三个文件,运行

Creat OK!
内存池信息 块数量3,块大小1024,使用个数0,起始地址0x00D82700,终止地址0x00D83310
得到内存块地址0x00D82700
内存池信息 块数量3,块大小1024,使用个数1,起始地址0x00D82700,终止地址0x00D83310
得到内存块地址0x00D82B08
内存池信息 块数量3,块大小1024,使用个数2,起始地址0x00D82700,终止地址0x00D83310
得到内存块地址0x00D82F10
内存池信息 块数量3,块大小1024,使用个数3,起始地址0x00D82700,终止地址0x00D83310
GetMemblock ERR !Pool Full!
得到内存块地址0x00000000
内存池信息 块数量3,块大小1024,使用个数3,起始地址0x00D82700,终止地址0x00D83310
释放第二个内存块0x00D82B08
内存池信息 块数量3,块大小1024,使用个数2,起始地址0x00D82700,终止地址0x00D83310
得到内存块地址0x00D82B08
内存池信息 块数量3,块大小1024,使用个数3,起始地址0x00D82700,终止地址0x00D83310
Destroy OK!


C/C++程序内存的各种变量存储区域和各个区域详解

【C语言】内存分区

c语言内存池原理及实现

高效内存池的设计方案[c语言]

malloc和free的实现原理解析

malloc 的实现原理 内存池 mmap sbrk 链表

  • 23
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小熊coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值