C语言内存堆

本文提供一种管理零散内存的方法,该方法封装了一个内存堆(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);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值