1. 程式人生 > >c語言介面與實現--再論記憶體管理含例項

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