附錄B 記憶體事件紀錄常式
本附录中的程序实作了一个 第三章 中提到过的简单联结串行版本的内存事件纪录例程。这程序故意写得简单易懂-并不是针对需要大量使用内存管理器的程序而设计的。不过在你花时间将这些例程改用 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