由於項目需要寫了一個求最短路徑的引擎。 運行穩定(內存,CPU,Handle count 保持未定),但是一段時間後,進程突然退出(crash?)。確切地說是計算了20000+ 對(源點、目標點)後。沒有捕捉到任何異常。就好像有人殺掉進程似的。通過分析在Adplus在進程退出前創建的進程dump,發現是CTRL+C導致進 程退出,堆棧信息顯示計算線程最後調用函數是_heap_alloc_dbg。 這個函數是被由new 或者malloc調用的。
難道是out of memory?可是performance monitor 現實內存穩定,並沒有洩漏。仔細分析dump,發現程序最後一個語句是dbgheap.c的338 行
if (lRequest == _crtBreakAlloc) //337
_CrtDbgBreak(); //338
根據msdn,_CrtDbgBreak 用來設定斷點。為什麼呢?閱讀_heap_alloc_dbg函數發現:
lRequest = _lRequestCurr;
if (lRequest == _crtBreakAlloc)
_CrtDbgBreak();
//省略如干行
++_lRequestCurr;
也就是說一旦_heap_alloc_dbg被調用,_lRequestCurr就增加1,搜索_lRequestCurr和_crtBreakAlloc,發現定於如下:
static long _lRequestCurr = 1;
_CRTIMP long _crtBreakAlloc = -1L;
並且只有函數_CrtSetBreakAlloc改變_crtBreakAlloc 。在我的程序中沒有地方調用_CrtSetBreakAlloc,所以_crtBreakAlloc 應該保持-1,所以只要_heap_alloc_dbg, 也就是說new 或malloc被不停的調用,即使請求的內存被正確釋放,條件lRequest == _crtBreakAlloc最終會滿足。Yes, that's it!!!
寫了一個簡單的小程序如下,證實了我的想法。
int nCount = 1;
while(1)
{
char* p = new char[4]; //cause _lRequestCurr to increase
delete []p;
nCount++;
if(nCount == 0xffffffff) //_lRequestCurr should reach this value already
break; //to ensure that if my thought is wrong, loop could break
}
所以我認為這是debug run-time library的一個bug。如果程序(compiled with vc6 debug version run-time library)中會不停的請求內存,那麼該程序最終會異常中止,只是時間問題。
所幸的是微軟在以後的版本中對這個做了修改,vs2005中已經沒喲這個bug了。具體不知道是從那個版本開始的。
資料來源:http://blog.csdn.net/Miracle08/archive/2006/12/24/1457060.aspx
遇到過一個通信方面的軟件,需要長期運行,做壓力測試時,高負荷連續運行一定天數時必定崩潰,而且都是在msvcrtd.dll中崩潰。負責維護的人百思不得其解,就去問微軟的人,結果微軟的人說這是VC6帶的msvcrtd.dll的一個問題,VC2005已經沒有這個問題了,請升級到新的版本。這個軟件規模比較大,依賴於很多庫,後台都是用VC6編譯的調試版本,為了方便定位問題,沒有Release版本。升級到VC2005後會不會出現別的問題,沒有人敢冒這個風險,於是沒有使用VC2005。
閒著沒事的時候分析了一下,才發現問題其實很簡單。msvcrtd.dll對每次內存申請都進行計數,當計數值達到設定的某個值時,就會調用_CrtDbgBreak()。MSDN對_CrtDbgBreak的說明是:Sets a break point on a particular line of code,其實_CrtDbgBreak在X86下只有一條指令就是int 3(0xCC)。
在dbgheap.c中定義了下面兩個變量:
static long _lRequestCurr = 1;
extern "C" _CRTIMP long _crtBreakAlloc = -1L;
_lRequestCurr表示當前的申請次數,_crtBreakAlloc表示當內存申請次數達到某個值時break,即調用_CrtDbgBreak。詳情可參考debugheap.c中的_heap_alloc_dbg_impl函數:
lRequest = _lRequestCurr;
if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)
_CrtDbgBreak();
VC6附帶的dbgheap.c中沒有添加_crtBreakAlloc != -1L的判斷,而是:
if (lRequest == _crtBreakAlloc)
_CrtDbgBreak();
_lRequestCurr初始化為1,每次申請內存都加1,當_lRequestCurr為-1時在VC6的dbgheap.c中就會觸發int 3導致程序退出,而在新的版本中添加了_crtBreakAlloc != -1L的判斷,所以默認的情況下是不會觸發int 3 退出的。
可以通過調用_CrtSetBreakAlloc設置_crtBreakAlloc的值,當我們設置了新的_crtBreakAlloc,而且_crtBreakAlloc等於_lRequestCurr時就會觸發int 3。
弄清楚了問題的所在,我們就可以著手解決問題了。VC6的dbgheap.c中有兩個地方判斷了lRequest 是否與_crtBreakAlloc相等,相等後執行指令int 3。我們不用複雜的處理,把int 3替換為nop(0x90)指令即可。首先得到「if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)」 對應的二進制指令,用UE打開msvcrtd.dll,使用16進制編輯模式,查找得到的二進制指令,發現確實只有二處,把緊接著它們的0xCC替換為0x90,問題解決。
資料來源:http://blog.csdn.net/someonea/archive/2008/03/29/2229183.aspx