VC6编译的Debug版本程序中存在的问题及解决方法
来源:互联网 发布:怎样注册淘宝会员 编辑:程序博客网 时间:2024/04/29 04:09
一、引言
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;
}
经简单测试发现效率基本没有受到影响。希望大家能讨论指正。此外,其中的行号可能跟实际略有出入。
- VC6编译的Debug版本程序中存在的问题及解决方法
- Oracle9i中OCCI在VC6下不能DEBUG的问题及解决方法
- Oracle9i中OCCI在VC6下不能DEBUG的问题及解决方法(转贴)
- VS2013 编译VC6.0等比较老版本的MFC工程字符集错误问题的解决方法
- 在VC6.0中关于Debug和Release两种版本的问题
- WINCE编译debug版本出错的问题
- vc6编译boost1.33.0版本正则库失败的解决方法
- vs2010 MFC程序编译过程中debug版本跟release版本的区别
- VC6.0存在的问题
- 深度学习存在的问题及解决方法
- vs2008编译的debug版本程序在没有装vs2008的机器上启动失败问题
- VC6转到换高版本的问题与解决方法
- win7中,VC6编译的程序中出现的COleDropTarget和WM_DROPFILES相冲突的问题
- vc6.0 中stl存在的问题 bug
- 浅谈VC6中Debug版本与Release版本生成的Exe的差异
- 浅谈VC6中Debug版本与Release版本生成的Exe的差异
- 浅谈VC6中Debug版本与Release版本生成的Exe的差异
- 浅谈VC6中Debug版本与Release版本生成的Exe的差异
- 全业务时代运维支撑体系
- HeadFirst 设计模式学习笔记8--模板方法模式
- 在Java中使用javascript函数
- Mysql 常用数值类型整理
- Flash显示灰度图像
- VC6编译的Debug版本程序中存在的问题及解决方法
- SQL 常用的 增、删、改、查 还是牢记于心 噢
- WINCE及MOBILE常用代码
- Android Wifi方法大全
- Cairngorm学习笔记
- socket基础知识
- c#中属性和域
- IRQ.s的理解
- Android 界面设计工具 droiddraw