本章开头指出上一章节描述的内存管理方法存在一些缺陷,比如不适合频繁创建和销毁内存的应用场景;所以在本章重新给出了另外一种设计思路。提出了内存池的概念,如果熟悉的小伙伴,还接触过线程池的概念。个人认为这一章节比前一章节更好理解。
本书中的代码有个地方的设计容易让人误解,在头文件中代码如下
#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中,如果再次申请,原来的数据都还在,所以如果真正使用是获得的内存空间记得初始化一下。