c语言接口与实现--再论内存管理含实例

本章开头指出上一章节描述的内存管理方法存在一些缺陷,比如不适合频繁创建和销毁内存的应用场景;所以在本章重新给出了另外一种设计思路。提出了内存池的概念,如果熟悉的小伙伴,还接触过线程池的概念。个人认为这一章节比前一章节更好理解。

本书中的代码有个地方的设计容易让人误解,在头文件中代码如下

#ifndef ARENA_INCLUDED
#define ARENA_INCLUDED

#include "except.h"

#define T Arean_T
// T* 和T名字一样,这样设计让人费解
typedef struct T *T;

extern const Except_T Arena_NewFailed;
extern const Except_T Arena_Failed;

extern T Arean_new (void);
extern void Arena_dispose(T *ap);

extern void *arena_alloc(T arena, long nbytes, const char *file, int line);
extern void *Arena_calloc(T arena, long count, long nbytes, const char *file, int line);

extern void Arena_free(T arena);


#endif

在源文件arena.c中也有宏定义

#define T Arena_T

这样就不容易分清楚代码中T是如何替换的,核对书中给的代码,可以看出凡是T前面含struct的则会用宏定义替代,即Arena_T,凡是无struct的会被typedef struct T* T替代,如下代码所示

struct T 
{
    T prev; // 这里的T,由typedef struct T* T替代
            // 替换后为 Arena_T * prev;
    char *avail;
    char *limit;
};

union header 
{
    struct T b; // 这里的T会被#define T Arena_T替代
                // 替代后为struct Arena_T b;
    union align a;
};

而且gcc编译器,创建结构体后定义结构体变量时如果不加struct会报错,如下面举例代码

struct st_tmp
{
    int idata;
};

// 有的gcc编译器在缺少struct的情况下会报错
static st_tmp tmp_a;
static struct st_tmp tmp_b; 

所以在书中给的代码进一步验证,如果不带struct,则由struct T *替代,而T则再次替换为Arena_T替换。

替换后的头文件为
arena_l.h

#ifndef ARENA_INCLUDED
#define ARENA_INCLUDED

#include "../../include/except.h"

#define T Arena_T
typedef struct T* PT;


extern const struct Except_T Arena_NewFailed;
extern const struct Except_T Arena_Failed;

extern struct Arena_T* Arean_new (void);
extern void Arena_dispose(struct Arena_T* *ap);

extern void *Arena_alloc(struct Arena_T* arena, long nbytes, const char *file, int line);
extern void *Arena_calloc(struct Arena_T* arena, long count, long nbytes, const char *file, int line);

extern void Arena_free(struct Arena_T* arena);

#define NEW(t,p) \
    ((p) = Arena_alloc(t, sizeof(*(p)), __FILE__, __LINE__))  

#undef T
#endif

arena.c书中原代码为

#include <stdlib.h>
#include <string.h>
#include "assert.h"
#include "except.h"
#include "arena.h"
#define T Arena_T

const struct  Except_T Arena_NewFailed = {"Arena Creation Failed"};
const Except_T Arena_Failed = {"Arena Allocation Failed"};



struct T 
{
    T prev;
    char *avail;
    char *limit;
};

union align {
    int i;
    long l;
    long *lp;
    void *p;
    void (*fp) (void);
    float f;
    double d;
    long double ld;
};

union header 
{
    struct T b;
    union align a;
};

static T freechunks;
static int nfree;
T Arena_new(void)
{
    T arena = malloc(sizeof(*arena));
    if(arena == NULL)
    {
        RAISE(Arena_NewFailed);
    }
    arena->prev = NULL;
    arena->limit = arena>avail = NULL;

    return arena;
}

void  Arena_dispose(T *ap)
{
    assert(ap && *ap);
    Arena_free(*ap);
    free(*ap);
    *ap = NULL:
}
void *Arena_alloc(T arena, long nbytes, const char *file, int line)
{
    assert(arena);
    assert(nbytes > 0);

    nbytes = ((nbytes + sizeof(union align) -1) / (sizeof(union align))) * (sizeof(union align));

    while (nbytes > arena->limit - arena->avail)
    {
        T ptr;
        char *limit;
        if ((ptr = freechunks) != NULL)
        {
            freechunks = freechunks->prev;
            nfree--;
            limit = ptr->limit;
        }else
        {
            long m = sizeof(union header) + nbytes + 10*1024;
            ptr = malloc(m);
            if(ptr == NULL)
            {
                if (file == NULL)
                {
                    RAISE(Arena_failed);
                }else
                {
                    Except_raise(&Arena_Failed, file, line);
                }
            }
            limit = (char *)ptr + m;
        }
        *ptr = *arena;
        arena->avail = (char *) ((union header *)ptr + 1);
        arena->limit = limit;
        arena->prev = ptr;
    }

    arena->avail += nbytes;
    return arena->avail - nbytes;
}

void * Arena_calloc(T arena, long count, long nbytes, const char *file, int line)
{
    void * ptr;
    assert(count > 0);
    ptr = Arena_alloc(arena, count *nbytes, file, line);
    memset(ptr, '\0', count *nbytes);
    return ptr;
}


void Arena_free(T arena)
{
    assert(arena);
    while(arena->prev)
    {
        struct T tmp = *arena->prev;
        if(nfree < THRESHOLD)
        {
            arena->prev->prev = freechunks;
            freechunks = arena->prev;
            nfree++;
            freechunks->limit = arena->limit;
        }else
        {
            free(arena->prev);
        }
        *arena = tmp;
    }

    assert(arena->limit == NULL);
    assert(arena->avail == NULL);
}

#define THRESHOLD 10

经过替换后的代码如下,这样就更好理解了
arena_n.c

#include <stdlib.h>
#include <string.h>
#include "assert.h"
#include "except.h"
#include "arena.h"
#define T Arena_T

const struct  Except_T Arena_NewFailed = {"Arena Creation Failed"};
const Except_T Arena_Failed = {"Arena Allocation Failed"};



struct Arena_T
{
    // T prev;
    struct Arena_T* prev;
    char *avail;
    char *limit;
};

union align {
    int i;
    long l;
    long *lp;
    void *p;
    void (*fp) (void);
    float f;
    double d;
    long double ld;
};

union header 
{
    // struct T b;
    struct Arena_T b;
    union align a;
};

// static T freechunks;
static struct Arena_T* freechunks;
static int nfree;

//T Arena_new(void)
struct Arena_T* Arena_new(void)
{
    // T arena = malloc(sizeof(*arena));
    struct Arena_T* arena = malloc(sizeof(*arena));
    if(arena == NULL)
    {
        RAISE(Arena_NewFailed);
    }
    arena->prev = NULL;
    arena->limit = arena->avail = NULL;

    return arena;
}

// void Arena_dispose(T *ap)
void  Arena_dispose(struct Arena_T* *ap)
{
    assert(ap && *ap);
    Arena_free(*ap);
    free(*ap);
    *ap = NULL:
}

// void * Arena_alloc(T arena, ...)
void *Arena_alloc(struct Arena_T* arena, long nbytes, const char *file, int line)
{
    assert(arena);
    assert(nbytes > 0);

    nbytes = ((nbytes + sizeof(union align) -1) / (sizeof(union align))) * (sizeof(union align));

    while (nbytes > arena->limit - arena->avail)
    {
        // T ptr;
        struct Arena_T* ptr;
        char *limit;
        if ((ptr = freechunks) != NULL)
        {
            freechunks = freechunks->prev;
            nfree--;
            limit = ptr->limit;
        }else
        {
            long m = sizeof(union header) + nbytes + 10*1024;
            ptr = malloc(m);
            if(ptr == NULL)
            {
                if (file == NULL)
                {
                    RAISE(Arena_failed);
                }else
                {
                    Except_raise(&Arena_Failed, file, line);
                }
            }
            limit = (char *)ptr + m;
        }
        *ptr = *arena; // 只要两个结构体类型相同是可以直接赋值的
        arena->avail = (char *) ((union header *)ptr + 1);
        arena->limit = limit;
        arena->prev = ptr;
    }

    arena->avail += nbytes;
    return arena->avail - nbytes;
}
//void * Arena_calloc(T arena,...)
void * Arena_calloc(struct Arena_T* arena, long count, long nbytes, const char *file, int line)
{
    void * ptr;
    assert(count > 0);
    ptr = Arena_alloc(arena, count *nbytes, file, line);
    memset(ptr, '\0', count *nbytes);
    return ptr;
}

// void Arena_T(T arena)
void Arena_free(struct Arena_T* arena)
{
    assert(arena);
    while(arena->prev)
    {
        //struct T tmp = *arena->prev;
        struct Arean_T tmp = *arena->prev;
        if(nfree < THRESHOLD)
        {
            arena->prev->prev = freechunks;
            freechunks = arena->prev;
            nfree++;
            freechunks->limit = arena->limit;
        }else
        {
            free(arena->prev);
        }
        *arena = tmp;
    }

    assert(arena->limit == NULL);
    assert(arena->avail == NULL);
}

#define THRESHOLD 10

下面主要阐述两个函数Arena_alloc和Arena_free,其他相对好理解;阅读这些代码需要充分理解设计这些方法的链表结构,数据结构在程序设计中的地位不用多说。闲言少叙,直接进入正题。
分配和回收内存空间,重点是要理解两个结构体,一个是由Arena_new生成的arena,一个arena对应一个内存池,一般来说只需要执行一次Arena_new;另外一个就是静态变量freechunks,用于回收或者释放内存空间。
这里写图片描述

下面结合代码说明

void *Arena_alloc(struct Arena_T* arena, long nbytes, const char *file, int line)
{
    assert(arena);
    assert(nbytes > 0);

    // 大小与align大小对齐,与前一章节一致
    nbytes = ((nbytes + sizeof(union align) -1) / (sizeof(union align))) * (sizeof(union align));

   // 如果申请空间nbytes大于已有空间,则进入循环,否则跳过
   // 如果是第一次申请空间arena->limit和arena->avail都是NULL,相减结果为0,必然进入到while循环
    while (nbytes > arena->limit - arena->avail)
    {
        // T ptr;
        struct Arena_T* ptr;
        char *limit;
        // 第一次进入freechunks静态变量不为NULL,执行freechunks=freechunks->prev后,freechunks==NULL,细节描述见下图
        if ((ptr = freechunks) != NULL)
        {
            freechunks = freechunks->prev;
            nfree--;
            limit = ptr->limit;
        }else
        {
            long m = sizeof(union header) + nbytes + 10*1024;
            ptr = malloc(m);
            if(ptr == NULL)
            {
                if (file == NULL)
                {
                    RAISE(Arena_failed);
                }else
                {
                    Except_raise(&Arena_Failed, file, line);
                }
            }
            limit = (char *)ptr + m;
        }
        *ptr = *arena;
        arena->avail = (char *) ((union header *)ptr + 1);
        arena->limit = limit;
        arena->prev = ptr;
    }

    arena->avail += nbytes;
    return arena->avail - nbytes;
}

这里写图片描述

如果ptr空间大小分配完了或者不够用,则重新malloc中,且是从arena之后插入新创建的ptr节点,借用书中的图片来说明
这里写图片描述

再来说说Arena_free函数,设置了空闲模块长度为10,大于10则调用free释放arena->prev节点,可以看到所有的操作都是针对arena的prev节点来的,而arena如果需要则是通过Arena_dispose函数实现。

// void Arena_T(T arena)
void Arena_free(struct Arena_T* arena)
{
    assert(arena);
    while(arena->prev)
    {
        //struct T tmp = *arena->prev;
        struct Arean_T tmp = *arena->prev;
        if(nfree < THRESHOLD)
        {
            arena->prev->prev = freechunks;
            freechunks = arena->prev;
            nfree++;
            freechunks->limit = arena->limit;
        }else
        {
            free(arena->prev);
        }
        *arena = tmp;
    }

    assert(arena->limit == NULL);
    assert(arena->avail == NULL);
}

这里写图片描述

简单实例
arena_main.c

#include <stdio.h>
#include "arena_l.h"

typedef struct STDATA
{
    unsigned char ucData;
    int iData;
    char *str;
    int (*func)(int,int);
}STDATA, *PDATA;

int calc(int a, int b)
{
    return (a+b);
}

void main(void)
{
    struct Arena_T * arena = Arena_new();
    PDATA pDat;
    STDATA *pArr[14];
    int i=0;
    while(i<3)
    {
        NEW(arena, pDat);
        if(pDat != NULL)
        {
            pDat->str = (char *)Arena_alloc(arena, 100, __FILE__, __LINE__);
            strcpy(pDat->str, "Hello, world");
        }
        pArr[i] = pDat; 
        i++;
    }
    Arena_free(arena);
    i=0;
    while(i<3)
    {
        NEW(arena, pDat);
        if(pDat != NULL)
        {
            pDat->str = (char *)Arena_alloc(arena, 100, __FILE__, __LINE__);
        }
        pArr[i] = pDat; 
        printf("pArr[%d] value:%s\n",i, pArr[i]->str);
        i++;
    }
}

执行结果
这里写图片描述

中间调用Arena_free并不是真正的free,而是将内存收回到freechunks中,如果再次申请,原来的数据都还在,所以如果真正使用是获得的内存空间记得初始化一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值