您的位置:首页 > 其它

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

2010-05-14 15:32 513 查看
一、引言

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

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



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

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种方法进行分析。


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

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



CODE,Line57
static long _lRequestCurr = 1;
/* Current request number */

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



CODE,Line384
++_lRequestCurr;
第59行声明定义了_crtBreakAlloc,该值为-1

CODE,Line59
_CRTIMP long _crtBreakAlloc = -1L;
再看第318行的函数_heap_alloc_dbg

CODE,Line318
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);
}
重点看上述代码中的红色黑体部分,如下:

CODE,Line334

lRequest = _lRequestCurr;


/* break into debugger at specific memory allocation */


if (lRequest == _crtBreakAlloc)


_CrtDbgBreak();

_crtBreakAlloc的值初始化为-1L,且其值不变。每次new或alloc时_lRequestCurr的值加1,当_lRequestCurr从1加到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)。

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

DBGHEAP.c的中函数_heap_alloc_dbg的341行(紫色粗体)
if(!(*_pfnAllocHook)(_HOOK_ALLOC, NULL, nSize, nBlockUse, lRequest, szFileName, nLine))

因此,在第一次调用New或Alloc时,做了如下动作:
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.
在下次执行New或Alloc时,转到第1步,此时lRequest的值比_crtBreakAlloc大2

在钩子函数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
通过直接设置_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);

}
}

分析:
根据上述钩子函数的调用流程,在第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)时,_crtBreakAlloc比lRequest的值小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;
}

经简单测试发现效率基本没有受到影响。希望大家能讨论指正。此外,其中的行号可能跟实际略有出入。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐