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