440
社区成员




1.预先分配内存:内存池是在程序运行初期,预先从系统申请一块较大连续内存空间的池式结构。
2.管理内存分配与回收:负责管理内存空间的分配与回收工作,当程序需要小块内存时,直接从内存池中取出合适的部分交付,而不是频繁向系统请求;当程序释放内存时,内存池将其回收,等待下次再分配,而非立刻归还给系统。
3.提高效率与减少内存碎片:减少因频繁系统调用带来的开销,提高内存分配的效率。同时降低内存碎片的产生,因为系统调用分配内存相对耗时,且零散的内存分配易产生碎片,内存池通过集中管理内存,有效解决了这些问题。
4.定制化内存管理:内存池可根据不同场景和需求,定制内存分配策略,以满足特定程序对内存的独特要求。
1.分配效率
内存池:在程序启动时预先从操作系统申请一大块内存。后续程序需要内存时,直接从内存池中分配,无需再进行系统调用,大大减少了时间开销,显著提升了内存分配的效率。
malloc/free:是 C 语言标准库提供的内存分配与释放函数。每次调用malloc时,它会向操作系统发起内存分配请求,这涉及用户态到内核态的切换,需要一定的时间开销。尤其在频繁分配小块内存的场景下,这种开销会不断累积,严重影响程序运行效率。
2.内存碎片
内存池:通过对预先申请的大块内存进行管理,能有效减少内存碎片。它可以采用特定的数据结构和算法,将内存分割成大小固定或按一定规则的小块进行分配。当小块内存被释放时,内存池能方便地将其重新整合,避免了内存碎片化问题,使内存空间始终保持较高的利用率。
malloc/free:频繁的内存分配和释放极易产生内存碎片。当程序分配不同大小的内存块后又释放部分块时,内存空间会变得零散。
外部碎片:表现为内存中存在许多分散的、不连续的小空闲区域,这些空闲区域分布在已分配的内存块之间
内部碎片:表现为每个已分配的内存块内部存在未被利用的空闲部分,它是隐藏在已分配内存块内部的
1.jemalloc
设计理念:设计理念是通用性和可扩展性。它不仅考虑了内存分配的性能,还注重对内存碎片的管理。
内存管理策略:
多级分配:将内存划分为不同的大小类别,每个类别有独立的分配器。
内存碎片管理:采用了先进的算法来减少内存碎片。它会尝试将空闲内存块合并,以提供更大的连续内存空间。
使用场景:适用于对内存碎片敏感、需要处理各种大小内存分配的应用程序。
2.tcmalloc
设计理念:更侧重于为多线程应用程序提供高性能的内存分配。
内存管理策略:
线程本地缓存:每个线程都有自己的本地缓存,当线程需要分配内存时,首先会在本地缓存中查找可用的内存块,避免了线程之间的锁竞争。
中央自由列表:用于管理全局的空闲内存块。当线程的本地缓存满了或者需要释放内存时,会将内存块放回中央自由列表。
使用场景:更适合多线程应用程序,特别是那些对内存分配速度要求较高的场景。
定长
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义内存页的大小为 4096 字节
#define MEM_PAGE_SIZE 0x1000
// 定义内存池结构体
typedef struct mempool_s {
// 每个内存块的大小
int blocksize;
// 空闲内存块的数量
int freecount;
// 指向空闲内存块链表的头指针
char *free_ptr;
// 指向内存池的起始地址
char *mem;
} mempool_t;
// 创建内存池
// 参数: m 为指向内存池结构体的指针, block_size 为每个内存块的大小
// 返回值: 成功返回 0, 失败返回 -1 或 -2
int memp_create(mempool_t *m, int block_size) {
// 检查指针是否为空
if (!m) return -1;
// 设置每个内存块的大小
m->blocksize = block_size;
// 计算空闲内存块的数量
m->freecount = MEM_PAGE_SIZE / block_size;
// 分配内存页
m->mem = (char *)malloc(MEM_PAGE_SIZE);
// 检查内存分配是否成功
if (!m->mem) {
return -2;
}
// 将分配的内存页初始化为 0
memset(m->mem, 0, MEM_PAGE_SIZE);
// 空闲内存块链表的头指针指向内存池的起始地址
m->free_ptr = m->mem;
int i = 0;
char *ptr = m->mem;
// 构建空闲内存块链表
for (i = 0; i < m->freecount; i++) {
// 将当前内存块的下一个指针指向下一个内存块的地址
*(char **)ptr = ptr + block_size;
// 移动指针到下一个内存块
ptr = ptr + block_size;
}
// 最后一个内存块的下一个指针置为 NULL
*(char **)ptr = NULL;
return 0;
}
// 销毁内存池
// 参数: m 为指向内存池结构体的指针
void memp_destory(mempool_t *m) {
// 检查指针是否为空
if (!m) return;
// 释放内存池分配的内存
free(m->mem);
}
// 从内存池中分配一个内存块
// 参数: m 为指向内存池结构体的指针
// 返回值: 成功返回分配的内存块的指针, 失败返回 NULL
void *memp_alloc(mempool_t *m) {
// 检查指针是否为空或没有空闲内存块
if (!m || m->freecount == 0) return NULL;
// 获取当前空闲内存块链表的头指针
void *ptr = m->free_ptr;
// 更新空闲内存块链表的头指针为下一个空闲内存块
m->free_ptr = *(char **)ptr;
// 空闲内存块数量减 1
m->freecount--;
return ptr;
}
// 将内存块释放回内存池
// 参数: m 为指向内存池结构体的指针, ptr 为要释放的内存块的指针
void memp_free(mempool_t *m, void *ptr) {
// 将释放的内存块插入到空闲内存块链表的头部
*(char **)ptr = m->free_ptr;
// 更新空闲内存块链表的头指针为释放的内存块
m->free_ptr = (char *)ptr;
// 空闲内存块数量加 1
m->freecount++;
}
// 主函数, 用于测试内存池的功能
int main() {
// 定义一个内存池结构体变量
mempool_t m;
// 创建内存池, 每个内存块大小为 32 字节
memp_create(&m, 32);
// 从内存池中分配一个内存块
void *p1 = memp_alloc(&m);
printf("memp_alloc : %p\n", p1);
// 从内存池中分配一个内存块
void *p2 = memp_alloc(&m);
printf("memp_alloc : %p\n", p2);
// 从内存池中分配一个内存块
void *p3 = memp_alloc(&m);
printf("memp_alloc : %p\n", p3);
// 将内存块 p2 释放回内存池
memp_free(&m, p2);
return 0;
}
定长分配是一种内存管理策略,它将一块连续的内存空间划分为多个固定大小的内存块。
1.内存池初始化
在memp_create
函数中,会分配一块大小为MEM_PAGE_SIZE
的连续内存空间作为内存池。然后,根据指定的block_size
将内存池划分为多个固定大小的内存块,并通过链表的方式将这些空闲内存块连接起来。
2.内存分配
在memp_alloc
函数中,当需要分配内存时,会从空闲内存块链表的头部取出一个内存块,并将空闲内存块链表的头指针指向下一个空闲内存块。同时,空闲内存块的数量减 1。
3.内存释放
在memp_free
函数中,当需要释放内存时,会将释放的内存块插入到空闲内存块链表的头部,并更新空闲内存块链表的头指针。同时,空闲内存块的数量加 1。
定长分配可以有效地减少内存碎片的产生,提高内存分配和释放的效率。因为每个内存块的大小是固定的,所以在分配和释放内存时不需要进行复杂的内存管理操作,只需要简单地操作链表即可。
不定长
// 定义内存池节点结构体
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);
// 创建内存池
// 参数 pool 为指向内存池结构体的指针,size 为内存池节点的初始大小
// 返回值:成功返回 0,失败返回 -1
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;
}
// 销毁内存池
// 参数 pool 为指向内存池结构体的指针
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);
}
}
// 清空大内存块链表
pool->large = NULL;
// 指向内存池节点链表的头节点
mp_node_t *node = pool->head;
// 修正原代码中判断条件错误,这里应该是 node 不为空
while (node) {
// 保存下一个节点的指针
mp_node_t *tmp = node->next;
// 释放当前节点
free(node);
// 移动到下一个节点
node = tmp;
}
}
// 从内存池分配一个新的节点块
// 参数 pool 为指向内存池结构体的指针,size 为需要分配的内存大小
// 返回值:分配成功返回指向分配内存的指针,失败情况未处理完整
static void *mp_alloc_block(mp_pool_t *pool, size_t size) {
// 原代码此处 mem 未定义,应修改为 malloc 分配内存
void *mem = malloc(pool->max);
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;
// 获取当前可分配的内存起始位置
void *ptr = node->last;
// 更新可分配内存的起始位置
node->last += size;
// 找到内存池节点链表的尾节点
mp_node_t *iter = pool->head;
while (iter->next != NULL) {
iter = iter->next;
}
// 将新节点插入到链表尾部
iter->next = node;
return ptr;
}
// 从内存池分配大内存块
// 参数 pool 为指向内存池结构体的指针,size 为需要分配的大内存块大小
// 返回值:分配成功返回指向分配内存的指针,失败返回 NULL
static void *mp_alloc_large(mp_pool_t *pool, size_t size) {
// 检查指针是否为空
if (!pool) return NULL;
// 直接调用系统的 malloc 分配大内存块
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_large_t *)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;
}
// 从内存池分配内存
// 参数 pool 为指向内存池结构体的指针,size 为需要分配的内存大小
// 返回值:分配成功返回指向分配内存的指针,失败返回 NULL
void *mp_alloc(mp_pool_t *pool, size_t size) {
// 如果请求的内存大小超过内存池节点可分配的最大大小
if (size > pool->max) {
// 调用分配大内存块的函数
return mp_alloc_large(pool, size);
}
// 请求的内存大小小于等于内存池节点可分配的最大大小
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);
}
// 释放内存池中的内存
// 参数 pool 为指向内存池结构体的指针,ptr 为要释放的内存指针
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);
// 将该大内存块结构体的 alloc 指针置为 NULL
l->alloc = NULL;
return;
}
}
// 对于小内存块,这里未做具体释放操作,因为小内存块是按节点整体管理
}
核心思想是将内存分配分为小内存块和大内存块两种情况分别处理,以提高内存分配和释放的效率,减少内存碎片。
1.内存池结构
mp_pool_t:这是内存池的核心结构体,包含三个重要成员
max:表示内存池节点可分配的最大内存大小
head:指向内存池节点链表的头节点,这些节点用于分配小内存块
large:指向大内存块链表的头节点,用于管理超过max大小的大内存块
mp_node_t:小内存块结构体,用于管理小内存块,last
指针指向当前节点未使用内存的起始位置,end
指针指向当前节点内存的结束位置,next
指针指向下一个节点。
mp_large_t:大内存块结构体,alloc
指针指向实际分配的大内存块,next
指针指向下一个大内存块。
2.内存分配
小内存块分配:当请求的内存大小size
小于等于pool->max
时,会遍历内存池节点链表,检查每个节点是否有足够的空间。如果有,则从该节点分配内存并更新last
指针;如果所有节点都没有足够的空间,则调用mp_alloc_block
函数分配一个新的节点块。
大内存块分配:当请求的内存大小size
大于pool->max
时,直接调用系统的malloc
函数分配大内存块。然后会先遍历大内存块链表,查找是否有未使用的节点,如果有则将其指向新分配的大内存块;如果没有,则从内存池分配一个大内存块结构体,并将其插入到大内存块链表头部。
3.内存释放
大内存块释放:当调用mp_free
函数释放内存时,会先遍历大内存块链表,找到要释放的大内存块,然后调用free
函数释放该大内存块,并将该大内存块结构体的alloc
指针置为 NULL。
小内存块释放:小内存块是按节点整体管理的,在销毁内存池时会统一释放所有节点的内存。
使用不定长内存池可以高效地处理不同大小的内存分配请求,同时减少了频繁调用系统
malloc
和free
带来的开销和内存碎片问题。