首页  编辑  

附錄B 記憶體事件紀錄常式

Tags: /超级猛料/Book.凤毛麟角(电子书籍片断)/完美程式設計指南/   Date Created:

附錄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