内存池

前言:
和线程池一致,减少对资源创建和销毁的系统调用(malloc free); 此外,对于一个长期运行的进程,没有算法对内存进行规划,一个内存空间由于每个对象的大小不同,出现许多内存碎片。 因此,引入内存管理组件,比较通用 jemalloc tcmalloc。实际的内存池,往往针对大文件,小文件有多个内存池。 虽然实际工作不会开发内存池,但是自己的锻炼仍有意义。

目标:
1、定长数据的内存池; 2、不定长的内存池;

内存结构
前面我们学习了标准进程下情况C/C++中存在 动态数据区、静态数据区、代码段(文本段)
image
其中.开头表示的区域,是在编译阶段就已经确定的;占用固定大小区域,并有编译器自动分配与释放; 动态数据区的数据是在运行过程中使用的;;具体的,动态数据区有堆和栈,堆向高地址生长,栈向低地址生长;静态数据区包括bss段、静态数据data段和只读数据段rodata,bss段是未初始化或初始为0的数据,固定大小,运行时可读可写,而data和rodata都正常初始化了,代码段存储编译后的CPU指令,固定大小,只读且保持不变。

设计思路:
定长数据的内存池
关键在于找到每次新创建的块,应该在哪?考虑一种问题,已创建的块1,2,3;释放了2.后面再次创建时,应该创建2。 为了实现这个采用了free_ptr指向链表头部,使用头插法,将已经释放的块加入到链表中,后续空闲内存,都通过这个链表取用。

typedef struct mempool_s {

	int blocksize;
	int freecount;
	char *free_ptr;
	char *mem;

} mempool_t; 

C中高明的链表构造方法

	int i = 0;   //这个是链表
	char *ptr = m->mem;
	for (i = 0;i < m->freecount;i ++) {
		*(char **)ptr = ptr + block_size;
		ptr = ptr + block_size;
	} 
	*(char **)ptr = NULL;

不定长的内存池

typedef struct mp_node_s { //小块
	unsigned char *last;
	unsigned char *end;
	struct mp_node_s *next;
} mp_node_t;

typedef struct mp_large_s {
	struct mp_large_s *next;
	void *alloc;
} mp_large_t;


typedef struct mp_pool_s {
	size_t max; //大小内存临界
	struct mp_node_s *head;
	struct mp_large_s *large;
} mp_pool_t;

解释一下思路:
对于小块(设计上大小小于指定大小认为是小块)放置到指定小块页下面,mp_node_s就是该页的头部,last表示该页的数据尾部(从这里新增数据),end表示整个页的尾部(判断剩余数据空间是否可放置新数据),next是下一个页指针。 新来的小数据都放置到last后面,如果空间不足,新建页在放置。
对于大块,其结构体mp_large_s 也当做小块内存,放置在小块页下,next表示所有大块内存组成一个链表,alloc指向了大块数据的地址。建立大内存时,如果没有节点,创造一个,如果有且alloc==NULL,选用它, 我们只实现了大块内存的释放(释放指定ptr下的alloc,并alloc=NULL)

思考:
1、小块没能实现,释放后复用,如何实现?

2、大内存的建立是尾插法 如果改为头插法呢?

代码


#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct mp_node_s { //小块
	unsigned char *last;
	unsigned char *end;
	struct mp_node_s *next;
} mp_node_t;

typedef struct mp_large_s {
	struct mp_large_s *next;
	void *alloc;
} mp_large_t;


typedef struct mp_pool_s {
	size_t max; //大小内存临界
	struct mp_node_s *head;
	struct mp_large_s *large;
} mp_pool_t;


int mp_create(mp_pool_t *pool, size_t size);
void mp_destory(mp_pool_t *pool);
void *mp_alloc(mp_pool_t *pool, size_t size);
void mp_free(mp_pool_t *pool, void *ptr);



// size : 4096  池
int mp_create(mp_pool_t *pool, size_t size) {

	if (!pool || size <= 0) return -1;

	void *mem = malloc(size);


	struct mp_node_s *node = (struct mp_node_s *)mem;
	node->last = (char *)mem + sizeof(struct mp_node_s);
	node->end = (char *)mem + size;
	node->next = NULL;

	pool->head = node;
	pool->max = size;
	pool->large = NULL;

	return 0;
}

void mp_destory(mp_pool_t *pool) {

	mp_large_t *l;

	for (l = pool->large;l;l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	mp_node_t *node = pool->head;

	while (node) {
		mp_node_t *tmp = node->next;
		free(node);
		node = tmp;
	}
	pool->head = NULL;
    pool->large = NULL;
}


// 
static void *mp_alloc_block(mp_pool_t *pool, size_t size) {
	// 分配一个新块,大小与初始块相同(pool->max)
    char *mem = (char *)malloc(pool->max);
    if (!mem) return NULL;

	struct mp_node_s *node = (struct mp_node_s *)mem;
	node->last = (char *)mem + sizeof(struct mp_node_s);
	node->end = (char *)mem + pool->max;
	node->next = NULL;


	// tail insert --> head insert	
	mp_node_t *iter = pool->head; // 尾插到小内存块链表
	while (iter->next != NULL) {
		iter = iter->next;
	}
	iter->next = node;

    // 从新节点中分配 size 字节
    void *ptr = node->last;
    node->last += size;
    return ptr;
}

// 
static void *mp_alloc_large(mp_pool_t *pool, size_t size) {
	if (!pool) return NULL;  

	void *ptr = malloc(size);
	if (ptr == NULL) return NULL;

	mp_large_t *l;  
	for (l = pool->large;l;l = l->next) {
		if (l->alloc == NULL) {
			l->alloc = ptr;
			return ptr;
		}
	}

	
	l = mp_alloc(pool, sizeof(mp_large_t));
	if (l == NULL) {
		free(ptr);
		return NULL;
	}
	l->alloc = ptr;

	l->next = pool->large;//将新节点插入到大块链表的头部
	pool->large = l;
	
	return ptr;
}


void *mp_alloc(mp_pool_t *pool, size_t size) {

	if (size > pool->max) {
		// malloc_large;

		return mp_alloc_large(pool, size);
	} 

	// size <= pool->max
	void *ptr = NULL;
	
	mp_node_t *node = pool->head;

	do {

		if (node->end - node->last > size) {
			ptr = node->last;
			node->last += size;

			return ptr;
		}   //找到尾部节点的尾部
		node = node->next;
		
	} while (node);

	//需要新建新节点

	return mp_alloc_block(pool, size);
	

}

// 
void mp_free(mp_pool_t *pool, void *ptr) {//只释放大块

	mp_large_t *l;

	for (l = pool->large;l;l = l->next) {
		if (l->alloc == ptr) {
			free(l->alloc);
			l->alloc = NULL;
			return ;
		}
	}

	// 
	
}

/* ========== 测试主函数 ========== */
int main() {
    mp_pool_t pool;
    int ret = mp_create(&pool, 1024);   // 初始块大小 1024 字节
    if (ret != 0) {
        printf("create failed: %d\n", ret);
        return 1;
    }
    printf("Pool created, max=%zu\n", pool.max);

    // 1. 分配几个小块(小于 1024)
    void *s1 = mp_alloc(&pool, 200);
    void *s2 = mp_alloc(&pool, 300);
    printf("Small alloc: s1=%p, s2=%p\n", s1, s2);

    // 2. 分配一个大块(大于 1024)
    void *big1 = mp_alloc(&pool, 1500);
    printf("Large alloc #1: %p\n", big1);

    // 3. 再分配一个大块,此时 large 链表只有一个节点(big1),且没有空闲节点,会创建新 large 节点
    void *big2 = mp_alloc(&pool, 1200);
    printf("Large alloc #2: %p\n", big2);

    // 4. 释放 big1
    mp_free(&pool, big1);
    printf("Freed big1\n");

    // 5. 再次分配一个大块,应复用 big1 对应的 large 节点(alloc 被置 NULL 后重新赋值)
    void *big3 = mp_alloc(&pool, 800);
    printf("Large alloc #3 (should reuse node): %p\n", big3);

    // 6. 检查 large 链表状态(遍历打印)
    printf("\nLarge list:\n");
    mp_large_t *l = pool.large;
    while (l) {
        printf("  node=%p, alloc=%p\n", (void*)l, l->alloc);
        l = l->next;
    }

    // 7. 释放所有资源
    mp_destory(&pool);
    printf("Pool destroyed.\n");
	printf("Pool over.\n");
    return 0;
}
posted @ 2026-08-04 11:13  超级麋鹿  阅读(6)  评论(0)    收藏  举报