VC6编译的Debug版本程序中存在的问题及解决方法
2010-05-14 15:32
513 查看
一、引言
VC6编译的Debug版本程序中如果用到了new或alloc,那么迟早有一天程序会抛出异常。
首先对问题发生的原因进行了分析;其次对其中一种方法的可行性进行了验证,并进行了详细的说明;再次,分析了几个实例;最后是小结。
提供的四种解决方案如下:
这里只针对第3种方法进行分析。
二、VC6的Debug版本中存在的问题及原因
在Microsoft Visual Studio/VC98/CRT/SRC目录下有个DBGHEAP.C文件(一般情况下会有该文件)。其中第57行声明定义了一个计数器_lRequestCurr,初始值为1。
每次new()或malloc()调用,计数器_lRequestCurr会加1,从318行函数_heap_alloc_dbg中第384中的蓝色黑体部分可以看到。
第59行声明定义了_crtBreakAlloc,该值为-1。
再看第318行的函数_heap_alloc_dbg:
重点看上述代码中的红色黑体部分,如下:
_crtBreakAlloc的值初始化为-1L,且其值不变。每次new或alloc时_lRequestCurr的值加1,当_lRequestCurr从1加到2147483647,再加到-2147483648
,再加到-1之后,if
(lRequest == _crtBreakAlloc)的条件便成立了,此时调用_CrtDbgBreak(),因此发生异常。
注:在我debug测试代码时,虽然_lRequestCurr的初始值为1,但是第一次执行到lRequest
= _lRequestCurr时_lRequestCurr的初始值不是1,而是48,之后每次加1。
三、解决方法:
使用第三种方法:
四、实例
四、小结
注册钩子函数的方法解决了VC6编译的Debug版本中存在的问题,根据EXAMPLE
III可以得出解决方法如下:
经简单测试发现效率基本没有受到影响。希望大家能讨论指正。此外,其中的行号可能跟实际略有出入。
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... |
二、VC6的Debug版本中存在的问题及原因
在Microsoft Visual Studio/VC98/CRT/SRC目录下有个DBGHEAP.C文件(一般情况下会有该文件)。其中第57行声明定义了一个计数器_lRequestCurr,初始值为1。
CODE,Line57 |
static long _lRequestCurr = 1; /* Current request number */ |
CODE,Line384 |
++_lRequestCurr; |
CODE,Line59 |
_CRTIMP long _crtBreakAlloc = -1L; |
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(); |
,再加到-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; } |
相关文章推荐
- [VS2008] Debug版本程序发布后 由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题 解决方法
- QT项目在VS上的编译,windows.h与winsock2.h存在多个重定义的问题的解决方法。
- 解决Linux程序编译链接动态库版本的相关问题
- 关于visual stdio 2008下编译程序汉语乱码及英文全角的问题解决方法
- 在WIN10系统下运行VS2013编译成功后出现:无法启动程序“.exe” 系统找不到指定文件的问题的解决方法
- VC6安装SP6补丁的方法(超完整!不看后悔) 也即解决VC6编译链接问题最完整的方法!
- Android 工程编译 Unsupported major.minor version 51.0 错误解决方法(JDK版本问题)
- windows命令行下随时随地调用csc编译器的方法(解决dos下编译.net程序找不到CSC的问题)
- MyEclipse不能自动编译解决方法总结(转)/debug时断点乱跳问题的解决办法
- 程序终于编译过去了,记录下碰到的问题及解决方法
- 使用eclipse编译qt程序遇到问题的解决方法
- linux 程序在高版本编译后到低版本运行报错:Floating point exception ,解决方法。
- 解决部分在Debug模式下程序没问题但是Release模式下出现问题的方法
- 在Dos命令行下编译Java程序问题和解决方法集锦
- VLC version(版本) : 3.0.0-git,2.1.5搭建编译时遇到的问题和解决方法!
- linux 内核编译:内核配置原理与常见配置问题的解决方法&&内核版本控制解析
- C++第六周任务一【任务1】下面的程序存在编译错误。有两种方法可以修改,请给出这两种修改方案,在报告中说明你倾向于用哪一种?为什么?处理此类问题的原则是什么?
- 一个VC6的工程转换为VS2008的工程后,编译找不到而且不能升级vc90.pdb文件的问题解决方法 2010-9-28 14:00
- 天漠SBC8100(OMAP3530)在Ubuntu11.04上编译内核是存在问题的解决方法