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中,如果再次申請,原來的資料都還在,所以如果真正使用是獲得的記憶體空間記得初始化一下。