荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jek (Super Like Cat), 信区: Program
标  题: 完美程式設計指南--附錄B 記憶體事件紀錄常式
发信站: 荔园晨风BBS站 (Thu Apr  4 07:04:30 2002), 转信


本附錄中的程式實作了一個 第三章 中提到過的簡單聯結串列版本的記憶體事件紀
錄常式。這程式故意寫得簡單易懂-並不是針對需要大量使用記憶體管理器的程式
而設計的。不過在你花時間將這些常式改用AVL-tree或B-tree等其他提供更快速搜
尋效率的資料結構改寫以前,先試試這程式是不是真的慢到不能用在你的程式中。
如果你沒使用許多整體配置記憶體塊的話,你應該會發現這程式對你一樣合用。

這檔案中的實作很簡單:對每個已配置的記憶體塊,這些副程式會多配置一些記憶
體來存放記錄了記憶體配置資訊的blockinfo結構。看看後頭的定義,當一個新的
blockinfo結構建立時,它的內容會被填好,並放置在連結串列結構前端-這串列
本身並沒有特別順序。再一次提醒你,用這樣的寫法只是因為它簡單易懂。

block.h
#ifdef DEBUG

/*-------------------------------
 *  blockinfo是個包含記憶體塊配置紀錄資訊的結構。每個已配置
 *  的記憶體塊都有在記憶體配置紀錄串列中有個對應的blockinfo
 *  結構。
 */

typedef struct BLOCKINFO
{
    struct BLOCKINFO *pbiNext;
    byte    *pb;                /* 記憶體塊起始位址     */
    size_t  size;               /* 記憶體塊長度         */
    flag    fReferenced;        /* 被參考過?           */
} blockinfo;                    /* 命名規則: bi, *pbi  */


flag fCreateBlockInfo(byte *pbNew, size_t sizeNew);
void FreeBlockInfo(byte *pbToFree);
void UpdateBlockInfo(byte *pbOld, byte *pbNew, size_t sizeNew);
size_t sizeofBlock(byte *pb);

void ClearMemoryRefs(void);
void NoteMemoryRef(void *pv);
void CheckMemoryRefs(void);
flag fValidPointer(void *pv, size_t size);

#endif
block.c
#ifdef DEBUG
/*---------------------------------------------------------
 *  這檔案中的函式必須比對任意指標,這是ANSI標準不保證具可
 *  攜性的動作。
 *  底下的巨集將本檔案中需要的指標比較動作分離出來。這實作
 *  假設使用的記憶體模式是平坦定址的,這樣子簡單的指標比較
 *  運算才會有效。底下的定義對於一些常見的80x86記憶體模式
 *  不適用。
 *
 *  譯註:  這些巨集對於不分段的16位元80x86記憶體模式與
 *  Win32環境下的平坦定址模式還是適用的。
 */

#define fPtrLess(pLeft, pRight)    ((pLeft) <  (pRight))
#define fPtrGrtr(pLeft, pRight)    ((pLeft) >  (pRight))
#define fPtrEqual(pLeft, pRight)   ((pLeft) == (pRight))
#define fPtrLessEq(pLeft, pRight)  ((pLeft) <= (pRight))
#define fPtrGrtrEq(pLeft, pRight)  ((pLeft) >= (pRight))

/*-------------------------------------------------------*/
/*           * * * * *  內部資料/函式  * * * * *            */
/*-------------------------------------------------------*/

/*---------------------------------------------------------
 *  pbiHead指向一個存放記憶體管理器除錯資訊的單向連結串列。
 */

static blockinfo *pbiHead = NULL;

/*---------------------------------------------------------
 *  pbiGetBlockInfo(pb)
 *
 *  pbiGetBlockInfo搜尋記憶體配置紀錄來找出pb指向的記憶體
 *  塊,並傳回一個指向存放該記憶體塊對應的記憶體配置資訊的
 *  blockinfo結構。
 *  註:pb必須指向一個已配置記憶體塊,不然你會碰到一個除錯
 *  檢查失敗;這函式只會成功,不然就觸發除錯檢查失敗的錯誤
 *  訊息-反正它絕對不會傳回錯誤狀態。
 *
 *      blockinfo *pbi;
 *      ...
 *      pbi = pbiGetBlockInfo(pb);
 *      //pbi->pb指向pb記憶體塊的起頭
 *      //pbi->size為pb指向的記憶體塊的大小
 */

static blockinfo *pbiGetBlockInfo(byte *pb)
{
    blockinfo *pbi;

    for (pbi = pbiHead; pbi != NULL; pbi = pbi->pbiNext)
    {
        byte *pbStart = pbi->pb;         /* 為了可讀性起見 */
        byte *pbEnd   = pbi->pb + pbi->size - 1;

        if (fPtrGrtrEq(pb, pbStart)  &&  fPtrLessEq(pb,
            pbEnd))
            break;
    }

    /*  找不到指標?情形是(a) 垃圾指標? (b) 指向已釋放記憶體?或
     是(c) 指向被fResizeMemory搬動了的記憶體塊?
     */
    ASSERT(pbi != NULL);

    return (pbi);
}


/*-------------------------------------------------------*/
/*              * * * * *  公開函式 * * * * *               */
/*-------------------------------------------------------*/


/*---------------------------------------------------------
 *  fCreateBlockInfo(pbNew, sizeNew)
 *
 *  這函式建立一份由pbNew:sizeNew定義的記憶體塊的紀錄項目。
 *  這函式在成功建議紀錄資訊時傳回TRUE, 不然傳回FALSE.
 *
 *      if (fCreateBlockInfo(pbNew, sizeNew))
 *          // 成功 – 記憶體配置紀錄資訊項目建立完成。
 *      else
 *          // 失敗 – 紀錄項目沒建立成功,把pbNew釋放掉。
 */

flag fCreateBlockInfo(byte *pbNew, size_t sizeNew)
{
    blockinfo *pbi;

    ASSERT(pbNew != NULL  &&  sizeNew != 0);
    pbi = (blockinfo a)malloc(sizeof(blockinfo));
    if (pbi != NULL)
    {
        pbi->pb = pbNew;
        pbi->size = sizeNew;
        pbi->pbiNext = pbiHead;
        pbiHead = pbi;
    }

    return (flag)(pbi != NULL);
}


/*---------------------------------------------------------
 *  FreeBlockInfo(pbToFree)
 *
 *  這函式毀掉pbToFree指向的記憶體塊的配置紀錄項目。pbToFree
 *  必須指向一塊已配置記憶體的開頭;不然你就會觸發除錯檢查
 *  失敗的錯誤訊息。
 *
 */

void FreeBlockInfo(byte *pbToFree)
{
    blockinfo *pbi, *pbiPrev;

    pbiPrev = NULL;
    for (pbi = pbiHead; pbi != NULL; pbi = pbi->pbiNext)
    {
        if (fPtrEqual(pbi->pb, pbToFree))
        {
            if (pbiPrev == NULL)
                pbiHead = pbi->pbiNext;
            else
                pbiPrev->pbiNext = pbi->pbiNext;
            break;
        }
        pbiPrev = pbi;
    }

    /* 如果pbi是NULL, pbtoFree的內容就無效。 */
    ASSERT(pbi != NULL);

    /* 在釋放記憶體之前毀掉*pbi的內容。 */
    memset(pbi, bGarbage, sizeof(blockinfo));

    free(pbi);
}


/*---------------------------------------------------------
 *  UpdateBlockInfo(pbOld, pbNew, sizeNew)
 *
 *  UpdateBlockInfo檢查pbOld指向的記憶體塊的紀錄資訊,然後
 *  更新紀錄資訊來反映記憶體塊位置換到了pbNew而大小改成了
 *  sizeNew個位元組長。pbOld必須指向一塊已配置記憶體的開頭,
 *  不然就會引發除錯檢查失敗的錯誤訊息。
 */

void UpdateBlockInfo(byte *pbOld, byte *pbNew, size_t
    sizeNew)
{
    blockinfo *pbi;

    ASSERT(pbNew != NULL  &&  sizeNew != 0);

    pbi = pbiGetBlockInfo(pbOld);
    ASSERT(pbOld == pbi->pb);

    pbi->pb = pbNew;
    pbi->size = sizeNew;
}


/*---------------------------------------------------------
 *  sizeofBlock(pb)
 *
 *  sizeofBlock傳回pb指向的記憶體塊的大小。pb必須指向一塊已
 *  配置記憶體的開頭;不然就會引發除錯檢查失敗的錯誤訊息。
 */

size_t sizeofBlock(byte *pb)
{
    blockinfo *pbi;

    pbi = pbiGetBlockInfo(pb);
    ASSERT(pb == pbi->pb);

    return (pbi->size);
}


/*-------------------------------------------------------*/
/*  底下的常式用來找出無效的指標跟遺失記憶體塊問題的。第三  */
/*  章中有這些常式的討論。                                  */
/*-------------------------------------------------------*/


/*---------------------------------------------------------
 *  ClearMemoryRefs(void)
 *
 *  ClearMemoryRefs將記憶體配置紀錄中所有的記憶體塊標示成為
 *  參考狀態。
 */

void ClearMemoryRefs(void)
{
    blockinfo *pbi;

    for (pbi = pbiHead; pbi != NULL; pbi = pbi->pbiNext)
        pbi->fReferenced = FALSE;
}


/*---------------------------------------------------------
 *  NoteMemoryRef(pv)
 *
 *  NoteMemoryRef將pv指到的記憶體塊標示成已參考狀態。註:
 *  pv不一定要指向記憶體塊開頭;只要指到已配置記憶體塊內任
 *  何一處即可。
 */

void NoteMemoryRef(void *pv)
{
    blockinfo *pbi;

    pbi = pbiGetBlockInfo((byte a)pv);
    pbi->fReferenced = TRUE;
}


/*---------------------------------------------------------
 *  CheckMemoryRefs(void)
 *
 *  CheckMemoryRefs從記憶體配置紀錄中找尋沒被NoteMemoryRef
 *  標示到的記憶體塊。如果這函式找到了未標示成已參考狀態的
 *  記憶體塊,就引發除錯檢查失敗的錯誤訊息。
 */

void CheckMemoryRefs(void)
{
    blockinfo *pbi;

    for (pbi = pbiHead; pbi != NULL; pbi = pbi->pbiNext)
    {
        /*  一個記憶體塊完整度的檢查。如果除錯檢查失敗了,就表示管
         *  理blockinfo的除錯碼出問題了,或者可能是有錯誤的記憶
         *  體寫入毀了記憶體配置紀錄的資料結構。無論何者,都是錯誤
         *  的情形。
         */

        ASSERT(pbi->pb != NULL  &&  pbi->size != 0);
        /*  對遺漏的記憶體塊進行檢查。如果除錯檢查失敗了,就表示程
         *  式遺失了一塊記憶體的配置紀錄,或是有整體記憶體塊的指標
         *  沒被NoteMemoryRef納入管理。
        */

        ASSERT(pbi->fReferenced);
    }
}


/*---------------------------------------------------------
 *  fValidPointer(pv, size)
 *
 *  fValidPointer核對pv指向的已配置記憶體塊的大小是否至少有
 *  size個位元組。如果pv不指向已配置記憶體,或是該記憶體塊
 *  的長度小於size  , fValidPointer就會發出除錯檢查失敗的錯誤訊
 *  息;這函是永遠不傳回FALSE.
 *
 *  fValidPointer傳回一個旗標(永遠為TRUE)的理由是為了讓你
 *  可以在一個ASSERT巨集內呼叫這函式。雖然這不式最有效率
 *  的用法,這樣子作巧妙地處理掉了除錯版本對發行版本控制的
 *  問題,而不是你操心#ifdef DEBUG或使用其他類似ASSERT巨
 *  集的問題。
 *
 *      ASSERT(fValidPointer(pb, size));
 */

flag fValidPointer(void *pv, size_t size)
{
    blockinfo *pbi;
    byte *pb = (byte a)pv;

    ASSERT(pv != NULL  &&  size != 0);

    pbi = pbiGetBlockInfo(pb);      /* 這裡核對pv的內容。*/

    /* 如果pb+size超出了記憶體塊的尾端了,size的內容就無效。*/
    ASSERT(fPtrLessEq(pb + size, pbi->pb + pbi->size));

    return (TRUE);
}

#endif






--
 === I love Puss forever ===

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.241]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店