本文提供一种管理零散内存的方法,该方法封装了一个内存堆(heap)结构体,并提供对应的接口。使用时先创建一个heap,然后每次内存申请都从heap中申请,最后统一释放heap。适用于短时间存在大量内存申请的情况,且这些申请的内存生命周期相同或相近。
引用:
C语言通用双向链表
- heap内部采用预分配大块内存的方式,然后分配时从中切割,减少系统函数malloc的调用次数,极大提高了效率。
- 申请时从大块内存中连续切割出小内存使用,空间利用率高。
- 多次申请内存,统一释放,降低内存管理成本。
一、数据结构
heap只需管理内存块的链表,每个内存块记录自己的使用信息。
typedef struct heap_block_struct heap_block_t;
struct heap_block_struct
{
byte* data; /* 数据区 */
uint32 size; /* 数据总大小 */
uint32 used; /* 已使用大小 */
LIST_NODE_T(heap_block_t) link;
};
typedef LIST_BASE_NODE_T(heap_block_t) heap_block_lst_t;
typedef struct heap_struct heap_t;
struct heap_struct
{
heap_block_lst_t lst_blocks;
};
添加一个新的块:
#define HEAP_BLOCK_EXTEND_SIZE (128 * 1024) /* 扩展的内存块大小 */
/* 功能: 添加一个块 */
heap_block_t* heap_add_block(
IN heap_t* heap,
IN uint32 size
)
{
heap_block_t* block;
assert(size > 0);
size = (size + 8- 1) & (~(8- 1)); /* 按8字节对齐 */
block = malloc(sizeof(heap_block_t) + size);
block->data = (byte*)(block + 1);
block->size = size;
block->used = 0;
LIST_ADD_LAST(link, heap->lst_blocks, block);
return block;
}
二、基础接口
1. 创建堆
/* 功能: 创建一个堆 */
heap_t* heap_create(
OPT IN uint32 size /* 可选参数, 第一个块的初始大小, 为0则使用默认大小 */
)
{
heap_t* heap;
heap = malloc(sizeof(heap_t));
LIST_INIT(heap->lst_blocks);
heap_add_block(heap, max(size, HEAP_BLOCK_EXTEND_SIZE));
return heap;
}
2. 释放堆
/* 功能: 释放一个堆 */
void heap_free(
IN heap_t* heap
)
{
heap_block_t* block;
block = LIST_GET_FIRST(heap->lst_blocks);
while (block)
{
LIST_REMOVE(link, heap->lst_blocks, block);
free(block);
block = LIST_GET_FIRST(heap->lst_blocks);
}
free(heap);
}
3. 从堆中申请内存(带初始化0)
/* 功能: 从堆中申请内存 */
void* heap_alloc(
IN heap_t* heap,
IN uint32 size
)
{
heap_block_t* cur_block;
void* mem;
size = (size + 8- 1) & (~(8- 1)); /* 按8字节对齐 */
cur_block = LIST_GET_LAST(heap->lst_blocks);
/* 如果空间不够要扩展一个块 */
if (cur_block->used + size > cur_block->size)
{
cur_block = heap_add_block(heap, max(size, HEAP_BLOCK_EXTEND_SIZE));
}
/* 获取内存, 初始化后返回 */
mem = cur_block->data + cur_block->used;
cur_block->used += size;
memset(mem, 0x00, size);
return mem;
}
4. 从堆中申请内存(不带初始化)
/* 功能: 从堆中申请内存 */
void* heap_alloc_fast(
IN heap_t* heap,
IN uint32 size
)
{
heap_block_t* cur_block;
void* mem;
size = (size + 8- 1) & (~(8- 1)); /* 按8字节对齐 */
cur_block = LIST_GET_LAST(heap->lst_blocks);
/* 如果空间不够要扩展一个块 */
if (cur_block->used + size > cur_block->size)
{
cur_block = heap_add_block(heap, max(size, HEAP_BLOCK_EXTEND_SIZE));
}
/* 获取内存, 直接返回 */
mem = cur_block->data + cur_block->used;
cur_block->used += size;
#ifdef DEBUG
memset(mem, 0xCD, size);
#endif
return mem;
}
三、封装其他常用接口
1. 字符串拷贝1
/* 功能: 从堆中申请内存并填充字符串 */
schar* heap_strdup(
IN heap_t* heap,
IN schar* str
)
{
schar* new_str;
new_str = heap_alloc_fast(heap, strlen(str) + 1);
strcpy(new_str, str);
return new_str;
}
2. 字符串拷贝2
/* 功能: 从堆中申请内存并填充字符串 */
schar* heap_strndup(
IN heap_t* heap,
IN schar* str,
IN uint32 n
)
{
schar* new_str;
new_str = heap_alloc_fast(heap, n + 1);
memcpy(new_str, str, n);
new_str[n] = ‘\0’;
return new_str;
}
3. 内存拷贝
/* 功能: 从堆中申请内存并填充 */
byte* heap_memcpy(
IN heap_t* heap,
IN byte* data,
IN uint32 n
)
{
byte* new_data;
new_data = heap_alloc_fast(heap, n);
memcpy(new_data, data, n);
return new_data;
}
4. 获取当前堆顶的指针
/* 功能: 获取当前堆顶 */
byte* heap_get_top(
IN heap_t* heap
)
{
heap_block_t* last_block;
last_block = LIST_GET_LAST(heap->lst_blocks);
return last_block->data + last_block->used;
}
5. 释放指定堆顶之后的内存
该接口配合 heap_get_top 使用,类似通过 heap_get_top 记录heap的某个状态,后续无论申请多少次内存,调用 heap_free_top 都可以直接恢复至该状态,在该状态之前申请的内存不受影响,便于某些数据结构的重用。
/* 功能: 释放指定堆顶后的内存 */
void heap_free_top(
IN heap_t* heap,
IN byte* top
)
{
heap_block_t* block;
/* 定位所处的内存块, 往前遍历, 这个块之后的块全部释放掉 */
block = LIST_GET_LAST(heap->lst_blocks);
while (block)
{
if ((block->data <= top) && (top <= block->data + (uint3264)block->size))
break;
/* 释放后重新获取 */
LIST_REMOVE(link, heap->lst_blocks, block);
free(block);
block = LIST_GET_LAST(heap->lst_blocks);
}
assert(block);
/* 重置可用大小 */
block->used = (uint32)(top - block->data);
}
测例
简单用法测试:
#define MEGA_BYTES ((uint32)(1024 * 1024))
void test_heap1()
{
heap_t* heap;
byte* data[100]; /* 指针数组, 指针指向的内存从heap中申请 */
schar* str[100];
uint32 i, j;
byte* heap_top;
int32 ct = 100;
/* 创建默认初始大小的堆 */
heap = heap_create(0);
/* 为数组前50个申请内存, 并设置值 */
for (i = 0; i < 50; i++)
{
data[i] = heap_alloc(heap, 1 * MEGA_BYTES);
memset(data[i], (int32)(i + 1), 1 * MEGA_BYTES);
str[i] = heap_strdup(heap, "data");
}
/* 记录当前heap top的位置(状态) */
heap_top = heap_get_top(heap);
/* 反复给数组后50个申请内存, 并设置值, 每次申请前都恢复到记录的状态 */
/* ct不论取多少, 内存都不会无限增长 */
while (ct--)
{
heap_free_top(heap, heap_top);
for (i = 50; i < 100; i++)
{
data[i] = heap_alloc(heap, 1 * MEGA_BYTES);
memset(data[i], (int32)(i + 1), 1 * MEGA_BYTES);
str[i] = heap_strdup(heap, "data");
}
}
/* 数据校验 */
for (i = 0; i < 100; i++)
{
for (j = 0; j < 1 * MEGA_BYTES; j++)
{
assert(data[i][j] == (i + 1));
}
assert(strcmp(str[i], "data") == 0);
}
/* 统一释放heap */
heap_free(heap);
}
使用的示例:
实际情况下,mydata结构会非常复杂,不一定所有的成员都会使用到,于是使用指针保存,用到了再去申请。而这些结构体指针可能还有指针的嵌套,或者链表等等。
这种复杂数据一般都是统一释放,但释放时如果采用malloc/free的方式,就非常麻烦,需要很多的判断,此外使用过程中可能还会临时申请内存,都需要考虑。
这时候使用heap就能有效减少管理成本,而且申请时的效率极高。
/* my data结构体 */
typedef struct my_data_struct my_data_t;
struct my_data_struct
{
heap_t* heap;
byte* heap_top;
schar* name;
byte* data;
uint32 data_len;
};
/* 功能: 创建my data */
my_data_t* my_data_create()
{
heap_t* heap;
my_data_t* mydata;
/* 创建heap, my data的所有内存都将从heap中申请 */
heap = heap_create(0);
mydata = heap_alloc(heap, sizeof(my_data_t));
mydata->heap = heap;
/* 记录初始状态 */
mydata->heap_top = heap_get_top(heap);
return mydata;
}
/* 功能: 释放my data */
void my_data_free(
my_data_t* mydata
)
{
heap_free(mydata->heap);
}
/* 功能: 设置my data的数据 */
void my_data_set(
my_data_t* mydata,
schar* name,
byte* data,
uint32 data_len
)
{
heap_t* heap;
heap = mydata->heap;
mydata->name = heap_strdup(heap, name);
mydata->data = heap_memcpy(heap, data, data_len);
mydata->data_len = data_len;
}
/* 功能: 清空my data数据, 便于重复使用 */
void my_data_clear(
my_data_t* mydata
)
{
heap_free_top(mydata->heap, mydata->heap_top);
}
void test_heap2()
{
my_data_t* mydata;
byte data[16] = { 0 };
/* 创建my data */
mydata = my_data_create();
/* 使用my data */
my_data_set(mydata, "name1", data, 16);
/* Do something... */
/* 清空my data */
my_data_clear(mydata);
/* 第二次使用mydata */
my_data_set(mydata, "name2", data, 13);
/* Do something... */
/* 释放mydata */
my_data_free(mydata);
}