VC6编译的Debug版本程序中存在的问题及解决方法

来源:互联网 发布:怎样注册淘宝会员 编辑:程序博客网 时间:2024/04/29 04:09

一、引言

VC6编译的Debug版本程序中如果用到了newalloc,那么迟早有一天程序会抛出异常。

首先对问题发生的原因进行了分析;其次对其中一种方法的可行性进行了验证,并进行了详细的说明;再次,分析了几个实例;最后是小结。

 

提供的四种解决方案如下:

1. Recompile the C-Runtime by yourself and change the code to:

  (but then you have to use own names instead of MSVCRT!)

       /* break into debugger at specific memory allocation */

       if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)

           _CrtDbgBreak();

2. Update to VC7/VC8/VC9

3. Register your own hook for allocation, and set the _crtBreakAlloc to a

valid value...:

 

int __cdecl MyAllocHook(

   int      nAllocType,

   void   * pvData,

   size_t   nSize,

   int      nBlockUse,

   long     lRequest,

   const unsigned char * szFileName,

   int      nLine

   )

{

  switch(nAllocType)

  {

  case _HOOK_ALLOC:

  case _HOOK_REALLOC:

   _crtBreakAlloc = lRequest-1;

   break;

  }

  return 1;

}

 

void main()

{

  _CrtSetAllocHook(MyAllocHook);

 

  char *pTest = NULL;

  // do arround 0x10 * 0x100000000 allocs (the lRequest-value wraps 0x10

times....)

  for (int a = 0; a < 10; a++)

  {

   for(int b = 0; b <= 0xffffffff; b++)

   {

     pTest = (char*) malloc(10);

     strcpy(pTest, "sodelle");

     free(pTest);

   }

   printf("/nWrap %d", b+1);

  }

}

4. Do not use the debug heap...

这里只针对3方法进行分析。

 

二、VC6Debug版本中存在的问题及原因

Microsoft Visual Studio/VC98/CRT/SRC目录下有个DBGHEAP.C文件(一般情况下会有该文件)。其中第57行声明定义了一个计数器_lRequestCurr,初始值为1

 

CODELine57

static long _lRequestCurr = 1;     /* Current request number */

 

每次new()malloc()调用,计数器_lRequestCurr1,从318行函数_heap_alloc_dbg中第384中的蓝色黑体部分可以看到。

 

CODELine384

++_lRequestCurr;

  

59行声明定义了_crtBreakAlloc,该值为-1

CODELine59

_CRTIMP long _crtBreakAlloc = -1L;

 

再看第318行的函数_heap_alloc_dbg

CODELine318

void * __cdecl _heap_alloc_dbg(

   size_t nSize,

   int nBlockUse,

   const char * szFileName,

   int nLine

   )

{

   long lRequest;

   size_t blockSize;

   int fIgnore = FALSE;

   _CrtMemBlockHeader * pHead;

   /* verify heap before allocation */

   if (_crtDbgFlag & _CRTDBG_CHECK_ALWAYS_DF)

       _ASSERTE(_CrtCheckMemory());

   lRequest = _lRequestCurr;

   /* break into debugger at specific memory allocation */

   if (lRequest == _crtBreakAlloc)

       _CrtDbgBreak();

   /* forced failure */

   if (!(*_pfnAllocHook)(_HOOK_ALLOC, NULL, nSize, nBlockUse, lRequest, szFileName, nLine))

   {

       if (szFileName)

       _RPT2(_CRT_WARN, "Client hook allocation failure at file %hs line %d./n",

           szFileName, nLine);

       else

       _RPT0(_CRT_WARN, "Client hook allocation failure./n");

       return NULL;

   }

   /* cannot ignore CRT allocations */

   if (_BLOCK_TYPE(nBlockUse) != _CRT_BLOCK &&

       !(_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF))

       fIgnore = TRUE;

   /* Diagnostic memory allocation from this point on */

   if (nSize > (size_t)_HEAP_MAXREQ ||

       nSize + nNoMansLandSize + sizeof(_CrtMemBlockHeader) > (size_t)_HEAP_MAXREQ)

   {

       _RPT1(_CRT_ERROR, "Invalid allocation size: %u bytes./n", nSize);

       return NULL;

   }

   if (!_BLOCK_TYPE_IS_VALID(nBlockUse))

   {

       _RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type./n");

   }

   blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;

#ifndef WINHEAP

   /* round requested size */

   blockSize = _ROUND2(blockSize, _GRANULARITY);

#endif /* WINHEAP */

   pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);

   if (pHead == NULL)

       return NULL;

   /* commit allocation */

   ++_lRequestCurr;

   if (fIgnore)

   {

       pHead->pBlockHeaderNext = NULL;

       pHead->pBlockHeaderPrev = NULL;

       pHead->szFileName = NULL;

       pHead->nLine = IGNORE_LINE;

       pHead->nDataSize = nSize;

       pHead->nBlockUse = _IGNORE_BLOCK;

       pHead->lRequest = IGNORE_REQ;

   }

   else {

       /* keep track of total amount of memory allocated */

       _lTotalAlloc += nSize;

       _lCurAlloc += nSize;

       if (_lCurAlloc > _lMaxAlloc)

       _lMaxAlloc = _lCurAlloc;

       if (_pFirstBlock)

       _pFirstBlock->pBlockHeaderPrev = pHead;

       else

       _pLastBlock = pHead;

       pHead->pBlockHeaderNext = _pFirstBlock;

       pHead->pBlockHeaderPrev = NULL;

       pHead->szFileName = (char *)szFileName;

       pHead->nLine = nLine;

       pHead->nDataSize = nSize;

       pHead->nBlockUse = nBlockUse;

       pHead->lRequest = lRequest;

       /* link blocks together */

       _pFirstBlock = pHead;

   }

   /* fill in gap before and after real block */

   memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);

   memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);

   /* fill data with silly value (but non-zero) */

   memset((void *)pbData(pHead), _bCleanLandFill, nSize);

   return (void *)pbData(pHead);

}

 

重点看上述代码中的红色黑体部分,如下:

CODELine334

   lRequest = _lRequestCurr;

   /* break into debugger at specific memory allocation */

   if (lRequest == _crtBreakAlloc)

       _CrtDbgBreak();

_crtBreakAlloc的值初始化为-1L,且其值不变。每次newalloc_lRequestCurr的值1,当_lRequestCurr1加到2147483647,再加到-2147483648 ,再加到-1之后,if (lRequest == _crtBreakAlloc)的条件便成立了,此时调用_CrtDbgBreak(),因此发生异常。

注:在我debug测试代码时,虽然_lRequestCurr的初始值为1,但是第一次执行到lRequest = _lRequestCurr_lRequestCurr的初始值不是1,而是48,之后每次加1

 

三、解决方法:

使用第三种方法:

CODE

int __cdecl MyAllocHook(

  int      nAllocType,

  void   * pvData,

  size_t   nSize,

  int      nBlockUse,

  long     lRequest,

  const unsigned char * szFileName,

  int      nLine

  )

{

 switch(nAllocType)

 {

 case _HOOK_ALLOC:

 case _HOOK_REALLOC:

   _crtBreakAlloc = lReques - 1;

   break;

 }

 return 1;

}

void main()

{

 //在程序入口加入如下代码,以及上边的钩子函数

 _CrtSetAllocHook(MyAllocHook);

 return 0;

} 

分析:

_CrtSetAllocHook方法是为了注册一个可以检查内存状况的钩子函数(类似于回调函数,或者说是一种回调函数),注册的钩子函数需符合以下格式:

MyAllocHook(int allocType, void* userData, size_t size, int blockType, long requestNumber, const unsigned char *filename, int lineNumber)

注册了钩子函数之后,每次调用newalloc时会调用钩子函数MyAllocHook,调用钩子函数的位置是:

DBGHEAP.c的中函数_heap_alloc_dbg341行(紫色粗体)

if(!(*_pfnAllocHook)(_HOOK_ALLOC, NULL, nSize, nBlockUse, lRequest, szFileName, nLine))

 

因此,在第一次调用NewAlloc时,做了如下动作:

1.      执行下面的代码

lRequest = _lRequestCurr;

/* break into debugger at specific memory allocation */

if (lRequest == _crtBreakAlloc)

_CrtDbgBreak();

lRequest 不等于_crtBreakAlloc,因为_crtBreakAlloc等于-1

2.      调用钩子函数MyAllocHook,传入参数lRequest,其值为_lRequestCurr的值,此时_crtBreakAlloc = lRequest - 1

3.      ++_lRequestCurr;

4.      在下次执行NewAlloc时,转到第1步,此时lRequest的值比_crtBreakAlloc2

 

在钩子函数MyAllocHook中传入了lRequest的值,并修改了_crtBreakAlloc的值,使之与(lRequest_lRequestCurr不等,这就是实现的基本原理。

 

四、实例

EXAMPLE I

通过直接设置_crtBreakAlloc为固定的_lRequestCurr很快会达到的来使异常立即发生。可以更清楚的追踪异常发生的原因。

#include <CRTDBG.H>

#include <windows.h>

#include <stdio.h>

 

int __cdecl MyAllocHook(

  int      nAllocType,

  void   * pvData,

  size_t   nSize,

  int      nBlockUse,

  long     lRequest,

  const unsigned char * szFileName,

  int      nLine

)

{

   switch (nAllocType)

   {

       case _HOOK_ALLOC:

       case _HOOK_REALLOC:

           _crtBreakAlloc =100;

           break;

       default:

           ;

   }

   return 1;

}

 

void main()

{

   //    LoadLibrary("BlackBox.dll");

   _CrtSetAllocHook(MyAllocHook);

 

   char *pTest = NULL;

   for (int a = 0; a < 10; a++)

   {

       for (int b = 0; b <= 0xffffffff; b++)

       {

           pTest = (char*) malloc(10);

           strcpy(pTest, "sodelle");

           free(pTest);

           //可以在这里打印_crtBreakAlloc的值来观察其运行情况

           //std::cout <<_crtBreakAlloc <<std::endl;;

       }

       printf("/nWrap %d", b + 1);

   }

}  

分析:

_crtBreakAlloc =100,不到100次的alloc操作之后,_lRequestCurr=100.

需要讨论的问题:

在我的机器上_lRequestCurr的值是从48开始,而不是从0开始,不知其原因。

 

EXAMPLE II

通过直接设置_crtBreakAlloclRequest + 1来使异常立即发生

#include <CRTDBG.H>

#include <windows.h>

#include <stdio.h>

 

int __cdecl MyAllocHook(

  int      nAllocType,

  void   * pvData,

  size_t   nSize,

  int      nBlockUse,

  long     lRequest,

  const unsigned char * szFileName,

  int      nLine

)

{

   switch (nAllocType)

   {

       case _HOOK_ALLOC:

       case _HOOK_REALLOC:

           _crtBreakAlloc = lRequest + 1;

           break;

       default:

           ;

   }

   return 1;

}

 

void main()

{

   //    LoadLibrary("BlackBox.dll");

   _CrtSetAllocHook(MyAllocHook);

 

   char *pTest = NULL;

   for (int a = 0; a < 10; a++)

   {

       for (int b = 0; b <= 0xffffffff; b++)

       {

           pTest = (char*) malloc(10);

           strcpy(pTest, "sodelle");

           free(pTest);

       }

       printf("/nWrap %d", b + 1);

   }

}  

分析:

根据上述钩子函数的调用流程,在第2步中_crtBreakAlloc = lRequest + 1时,在第3步中++_lRequestCurr,因此回到第1步中时lRequest_lRequestCurr)等于_crtBreakAlloc,因此立即发生异常。

 

EXAMPLE III

通过直接设置_crtBreakAlloc为固定的lRequest - 1来避免异常发生。

#include <CRTDBG.H>

#include <windows.h>

#include <stdio.h>

 

int __cdecl MyAllocHook(

  int      nAllocType,

  void   * pvData,

  size_t   nSize,

  int      nBlockUse,

  long     lRequest,

  const unsigned char * szFileName,

  int      nLine

)

{

   switch (nAllocType)

   {

       case _HOOK_ALLOC:

       case _HOOK_REALLOC:

           _crtBreakAlloc = lRequest - 1;

           break;

       default:

           ;

   }

   return 1;

}

 

void main()

{

   //    LoadLibrary("BlackBox.dll");

   _CrtSetAllocHook(MyAllocHook);

 

   char *pTest = NULL;

   for (int a = 0; a < 10; a++)

   {

       for (int b = 0; b <= 0xffffffff; b++)

       {

           pTest = (char*) malloc(10);

           strcpy(pTest, "sodelle");

           free(pTest);

       }

       printf("/nWrap %d", b + 1);

   }

}  

分析:

通上述分析。其实,令_crtBreakAlloc = lRequest也是可行的。在执行到

If(lRequest == _crtBreakAlloc)时,_crtBreakAlloclRequest的值小1

 

EXAMPLE IV

测试std::string

#include <CRTDBG.H>

#include <windows.h>

#include <stdio.h>

#include <iostream>

#include <string>

 

int __cdecl MyAllocHook(

  int      nAllocType,

  void   * pvData,

  size_t   nSize,

  int      nBlockUse,

  long     lRequest,

  const unsigned char * szFileName,

  int      nLine

)

{

   switch (nAllocType)

   {

       case _HOOK_ALLOC:

       case _HOOK_REALLOC:

           _crtBreakAlloc = lRequest - 1;

           break;

       default:

           ;

   }

   return 1;

}

 

void main()

{

   _CrtSetAllocHook(MyAllocHook);

 

   for (int a = 0; a < 10; a++)

   {

       for (int b = 0; b <= 0xffffffff; b++)

       {

           std::string a ="temp";

           std::cout <<_crtBreakAlloc << std::endl;

       }

       printf("/nWrap %d", b + 1);

   }

}

分析:

可看到以_crtBreakAlloc持续增加。

 

四、小结

注册钩子函数的方法解决了VC6编译的Debug版本中存在的问题,根据EXAMPLE III可以得出解决方法如下:

int __cdecl MyAllocHook(

  int      nAllocType,

  void   * pvData,

  size_t   nSize,

  int      nBlockUse,

  long     lRequest,

  const unsigned char * szFileName,

  int      nLine

  )

{

 switch(nAllocType)

 {

 case _HOOK_ALLOC:

 case _HOOK_REALLOC:

   _crtBreakAlloc = lReques;

   break;

 }

 return 1;

}

void main()

{

 //在程序入口加入如下代码,以及上边的钩子函数

 _CrtSetAllocHook(MyAllocHook);

 return 0;

} 

 

经简单测试发现效率基本没有受到影响。希望大家能讨论指正。此外,其中的行号可能跟实际略有出入。

 

 

 

原创粉丝点击