荔园在线
荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀
[回到开始]
[上一篇][下一篇]
发信人: 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软件 网络书店