方法一:如果你是C++程序員,如果你寫過一個很復雜的程序,如果你經(jīng)常碰到莫名其妙的崩潰問題。那么你就有可能遭遇了野指針。如果你比較細心,注意了Debug?。铮酰簦穑酰糨敵龃翱诘脑?,那么你就有可能注意到這樣一行提示:
HEAP: Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed
網(wǎng)絡上關于這個問題提問的人不少,但是真正給的出答案的卻少之又少。在網(wǎng)上搜索了幾天,終于發(fā)現(xiàn)了這個問題的解決之道。下面我來介紹一下。
GFlags是windows debug tools 工具包下的一個工具,在Windows 2000的Resource Kit中也可以找得到。用來設置一些調(diào)試屬性,總體上分為3個級別System, Kernel, 和 Image File。
我們設置好Path環(huán)境變量,將其指向Debug tools工具的目錄下。
輸入如下命令行:
gflags /p App.exe /full
或者 pageheap app.exe /full
該命令行會在注冊表里設置一些調(diào)試參數(shù),使內(nèi)存在使用的時候加入了保護機制,所以一旦內(nèi)存寫越界,或者發(fā)生野指針的問題,都會導致一個中斷。由此,你就可以確定問題到底出在哪里了。
方法二:
關于多線程中的內(nèi)存問題,必需足夠地重視,否則,程序總是會出現(xiàn)莫名其妙的錯誤。如果幻想某些情況“應該”不會出現(xiàn),或者是認為某些步驟是按照正常的順序下來的,那就錯誤大大地,因為有一個簡單的事實,就是你沒有遵守事實,想和CPU對著干!
xx SDK的報錯退出問題,從去年直到現(xiàn)在都沒有得到有效的解決,近段時間,被頻繁地爆露出來,是到了不得不直面的時候了,所以,前兩周,去了xx N次,直到現(xiàn)在,還不能確定問題是否真地解決了
SDK的現(xiàn)象:
在自己的測試demo上沒有問題,而在平臺上就有問題
后來的測試證明,不是demo沒有問題,而是原來的demo只是在跑自己測試的設備,其數(shù)量少,環(huán)境不真實,實際的情況無法得到有效的測試,所以,demo不會有任何問題
直接運行程序,就是出現(xiàn)調(diào)試的那種錯誤,在平臺的vc debug模式下,出現(xiàn)的是觸發(fā)了用戶斷點的錯誤。在SDK的vc debug模式下,爆露了錯誤的本質(zhì):
HEAP[pingtai.exe]: HEAP: Free Heap block 2be3000 modified at 2be3200 after it was freed
這種錯誤過去從沒有遇到過,經(jīng)查,表明是因為堆被破壞而產(chǎn)生錯誤。種種跡象表明,是因為某個內(nèi)存被刪除之后,其內(nèi)容又被修改了,這樣就是破壞了堆,就出現(xiàn)了這種錯誤,模擬程序如下:
void fun1()
{
char *p = new char[128];
delete[] p;
strcpy( p, "abcdefghijklmnopqrstuvwxyz" );
}
在我模擬的時候,其情況如下:
1. 如果我strcpy()的字節(jié)少于13個字節(jié),則不會報任何錯誤。此情況并不絕對
2. 當fun1()返回時,并不出錯,當程序退出時,報錯
3. 在fun1()返回后,再調(diào)用其它任何函數(shù),即會報錯
為方便后面的敘述,把錯誤的描述如下:
HEAP[<exe>]: HEAP: Free Heap block <addr1> modified at <addr2> after it was freed
對模擬程序的報錯進行分析,得出如下結論:
對內(nèi)存<addr1>進行了刪除,然后,又修改了內(nèi)存<addr2>處的內(nèi)容,其中,<addr2>是被包含在<addr1>中的,形式如下:
|------------------------------|
| <addr1> | <addr2> |
|------------------------------|
在模擬程序中,<addr1> = p,<addr2> >= p 且 <addr2> < p + 128
在真實的內(nèi)存中,<addr1>并不是等于p,而是會比p小,其不表示程序代碼中的任何一個內(nèi)存,而是在new時,用于保存p的內(nèi)存而產(chǎn)生的內(nèi)存指針,該指針由windows保存和維護(注:個人認為,通過計算,應該是可以得到p的地址),實際的模式如下:
|------------------------------|
| <addr1> | p |
|------------------------------|
<addr1>是windows占用的內(nèi)存,而p是程序占用的內(nèi)存
既然內(nèi)存的操作已經(jīng)明確,而且也知道了如何產(chǎn)生了這種錯誤,那么就要根據(jù)<addr2>來找是程序中哪個地方出現(xiàn)了這種情況,采用倒推 的方式,即首先根據(jù)<addr2>找到是哪個內(nèi)存被刪除后又重新進行了附值(或者是其中的某處被重新附值),然后再根據(jù)情況來使刪除和附值不 要沖突
程序的順序應該沒有問題,因為都是刪除后就不再進行附值操作,或者是其它任何使用的可能了,所以,從需要分配內(nèi)存(主要是char*類型的內(nèi)存分
配)變量開始找起。把所有new出來的char*指針地址及其長度打印出來,從上萬行的打印信息中一個一個查找,卻發(fā)現(xiàn)沒有<addr2>
這就奇怪了,難道是程序的執(zhí)行順序出現(xiàn)了問題?不可能吧
把所有new出來的類地址及其內(nèi)存范圍進行打印,再一行一行地仔細找,汗......
竟然是CAccept類出現(xiàn)了這種情況!難道其刪除之后,還有被使用的情況發(fā)生?根據(jù)以上內(nèi)存的分析,肯定是有這種使用情況存在,否則....,找!
因為CAccept的類使用較少,找起來也比較容易,主要就在兩個地方使用,一個是完成端口的死循環(huán)中,一個是任務檢查的死循環(huán)中,肯定是這兩個地方的使用有沖突。
完成端口的工作模式:
while( true )
{
if ( !getcompleteio() )
break;
if ( isaccept() )
{
CAccept *pacc = getaccept(); // 從列表中獲取
// 使用pacc,其中有附值操作
... ...
// 刪除pacc
SAFE_DELETE(pacc)
}
else
{
// 數(shù)據(jù)收發(fā)及socket端口的其它消息處理
}
}
任務檢查的工作模式:
while ( true )
{
wait(5000); // 等待5秒鐘,開始任務檢查
lock();
while ( not_end_accept_list )
{
CAccept *pacc = nextaccept();
if ( pacc->tooLongTime )
SAFE_DELETE(pacc) // 刪除 pacc
}
unlock();
// 連接檢查
...
}
對accept的操作子函數(shù)(比如createaccept()、getaccept()、deleteaccept()等),都是被放入臨界區(qū)中
的,這里仔細檢查發(fā)現(xiàn),完成端口中對CAccept的使用沒有被放入臨界區(qū)中,雖然其getaccept()中有臨界區(qū)的操作,雖然任務檢查中對
accept列表的操作也是在臨界區(qū)中進行的,那么,問題肯定就是出現(xiàn)在了這里。為了驗證,對任務檢查處的accept刪除和在完成端口中對accept
的使用進行信息打印,發(fā)現(xiàn),當完成端口中還沒有對其獲取的CAccept使用完時,已經(jīng)被任務檢查的循環(huán)刪除了!
這種情況在正常情況很難出現(xiàn),
為
了使它容易出現(xiàn),對臨界區(qū)的操作加了延時,即人為使每個操作都等待較長的時間(原來是沒有任何等待的),比如100毫秒,或者50毫秒等,這樣,可以使很
多的操作都被掛起,在設備的連接達到一定多的時候,這種掛起的操作會越集越多,采用這種方法,可以使一些問題較容易地復現(xiàn)出來
找到問題了,解決
起
來應該是比較容易的了(有些可是棘手的,比如完成端口中else分支里的socket消息的處理),只需要在完成端口中把對CAccept的使用也放入相
應的臨界區(qū)中即可,修改完畢,測試,再也沒有CAccept的堆問題了,至此,該部分的問題已“完美”地被解決了,那個痛快啊---------,就別提
了... ...
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
還有兩個問題,一個是關于上層對SDK的調(diào)用問題,一個是SDK內(nèi)部的問題。
這兩個問題,都和以上的問題的本質(zhì)一樣,就是刪除了后還在使用!
1. 上層對SDK的調(diào)用
SDK
已經(jīng)通知了上層設備斷開,但是,上層在得到該消息之后,竟然還在調(diào)用相應的接口。這本身不成問題,因為上層調(diào)用SDK的時候,SDK會從相關的設備列表中
獲取相應的連接,然后拿這個連接對設備進行操作,如果是該設備已經(jīng)斷開,那自然就會從相應的列表中刪除掉,那么在上層調(diào)用的API中獲取連接時,肯定是獲
取失敗,SDK在判斷后,自然直接返回失敗。沒錯!你說得太對了,就是這樣。...... 停!你想,仔細地想一想,然后再往下看
================================
在
SDK中,不知道上層對SDK的調(diào)用和消息接收,是在一個線程中還是兩個線程中,那么,SDK無從知道上層對SDK的消息處理和API調(diào)用的先后順序,有
一種情況,應該是比較容易理解的,當SDK向上層進行了很多的消息通知,上層可能是會把這個消息放入消息隊列中,然后一個一個地慢慢處理,其處理的某一個
消息,也許是10秒鐘之前的消息(有點夸張),如果是這樣,而且如果剛好這個消息就是連接斷開的消息,那么在其處理該消息之前的3秒鐘,用戶要對該設備下
發(fā)命令,用戶看到的結果就是,該設備在線,即下發(fā)命令失敗,而且顯示的錯誤消息是設備不在線!這樣,程序也不會出錯,因為7秒了,太久了,SDK應該是已
經(jīng)把這個連接從連接列表中刪除了。
但不幸的是,并不總是這種情況,而是有一個糟糕的情況可能會出現(xiàn)。當API函數(shù)中獲取到相應的連接后,正在向
設
備下發(fā)命令時,連接列表被任務檢查中的連接檢查循環(huán)刪除了(比如有1分鐘都沒有收到任何數(shù)據(jù)),這時,如果下發(fā)命令的函數(shù)中是讀取,而不進行任何的附值操
作,則出現(xiàn)的錯誤,應該是內(nèi)存非法讀的異常,否則,出現(xiàn)的錯誤,應該是和上面一樣的堆錯誤。這種錯誤,也許解決起來比較容易,比如在使用時,也放入臨界
區(qū),這樣當然可行,只是當有100個API時,你就必需得進行100次臨界區(qū)的操作,這不算重要,更重要的是,由于你在最外層增加了臨界區(qū)的操作,那么在
SDK的內(nèi)部,必需要小心地保證臨界區(qū)的操作不會產(chǎn)生沖突,否則,就會發(fā)生程序無反應的情況,需要使用任務管理器來結束的后果。如果其中有附值操作,這也
許是唯一的一種解決方法,但如果是沒有附值操作,則可能會比較簡單,比如,我只在那時出現(xiàn)了這個錯誤的API外層,增加了
try...catch()...,這樣,如果執(zhí)行的中間其實例被刪除了,則該API直接返回失敗。具體的解決方案,可以根據(jù)實際情況的不同,進行不同的
處理,這里,只是闡明我自己的想法而已
2. SDK內(nèi)部的另外一個問題
和CAccept完全一樣,只是其復雜度相當大,由于其幾乎涉及到程序的所有地方,所以,解決起來相當棘手,而且,由于其和要上層通訊,則一不小心,就會使臨界區(qū)沖突(除了上層的代碼之外,SDK中的代碼都被放入臨界區(qū)):
[收到socket消息] --> [通知上層] --> [上層調(diào)用SDK API] --> [API調(diào)用SDK內(nèi)部的函數(shù)]
這個過程中,因為SDK通知上層后,需要等到上層返回,才進行下一步處理,上層需要調(diào)用API的返回后自己才返回,而API調(diào)用內(nèi)部函數(shù)時候,可能需要先進入臨界區(qū)才能操作,如此便出現(xiàn)了臨界區(qū)的沖突。
所以,在處理收到的socket消息時,被放入臨界區(qū)是比較危險的做法,所以,這里如果要找到一個好的解決方案,比較棘手
由于時間問題,暫只淺談之.