這里還要再介紹幾個調試技術。大家使用這些調試技術,能使調試程序的工作變得更容易。
OutputDebugString函數
當程序運行時跟蹤程序的執行有時對用戶是很有幫助的;也許用戶希望在不使用斷點來暫停程序執行的情況下查看變量的值。使用OutputDebugString函數就能做到這些。這個函數是個使用方便的調試工具,但很多程序員卻忽視了它,主要原因是對它介紹的不夠。觀察下圖中Event Log窗口的最后一個入口,這一入口是用下面的代碼生成的:
procedure TForm1.btn1Click(Sender: TObject); begin OutputDebugString('In the btn1Click method...'); end;
這就是全部要做的。由於Delphi是系統調試器,因此,任何用函數OutputDebugString發送的字符串都會出現在Event Log窗口。可以在代碼中任何位置調用OutputDebugString函數。
要查看一個變量的值,必須按格式構成字符串,並把字符串發送給OutputDebugString函數,例如:
運行程序后,點擊btn1后,Event Log中顯示如下:
使用OutputDebugString函數可以查看程序運行的整個過程,即使是對實踐要求嚴格的代碼段。
追蹤查找存取違例
當一個程序試圖往不屬於它的內存寫入數據時,Windows會發出一條“Access Violation(存取違例)”出錯消息。所有的Windows程序員在開發應用程序時,都碰到過存取違例錯誤。
Note
在16位Windows中使用術語一般保護性錯誤GPF(General Protection Fault)。這一術語在32位Windows中也很流行,盡管32位Windows實際上產生Access Violation出錯信息,而不是GPF。
不論是初學者還是經驗豐富的Windows程序員,要追蹤查找存取違例,都是一件困難的事情。但是,隨着編寫Windows程序的經驗積累,程序員會逐漸對存取違例原因的查找產生產生第六感。下面講述一些線索供大家在查找違例時參考;並不是只有這些情況才會使程序產生存取違例,但它們是最常見的情況。
1、未初始化指針
未初始化指針是已經在程序中聲明過的指針,但還未給它賦一個有意義的內存地址值。未初始化指針包含的是隨機數據,最好的情況是它指向內存中無關緊要的地址,最壞的情況是它指向用戶程序所在的內存中的某個單元,這樣會導致不穩定的程序動作,因為每次運行程序時該指針可能指向不同的內存單元。應當在使用一個指針前和指針所指對象被刪除之后將該指針設置成nil(空指針)。當存取一個空指針時,程序會停止運行並報存取違例,調試器加亮顯示出錯源代碼行,這樣用戶就能立即查出有問題的指針。
2、刪除前面已刪除的指針
刪除一個已經被刪除的指針會導致存取違例。應該將已刪除的指針設成nil(空指針);刪除一個空指針是十分安全的。把刪除過的指針設置成nil(空指針),再刪除這個指針時就保證不會出錯。
3、數組覆蓋(Array Overwrites)
覆蓋一個數組的末尾可引起存取違例。在有些情況下,被覆蓋的內存不是關鍵性的,不會馬上顯現出問題,但過后不久程序還是因故障而停止運行。當這種情況發生時,用戶很可能到程序停止運行的地方去查找故障,但問題實際上是出在程序的其他地方。另外一種情形是:被覆蓋的內存很關鍵,因而程序會因故障而立即停止運行。極端情況下會造成Windows被破壞。
可通過范圍檢查把數組覆蓋的可能性減少到最低程度。當設置檢查范圍時(缺省設置),編譯器會對每一次數組引用作檢查,看看看所存取的數組元素是否超出了有效范圍。例如,下面這段代碼就會導致編譯器報錯:
這段代碼中要存取一個數組的30號元素,而此數組只有21個元素。編譯器會檢查出被存取的數組元素超出了數組聲明的范圍,並產生一個編譯器錯誤,出錯信息如下:
但是,檢查范圍對變量無效。例如,下面的代碼就不會導致編譯報錯:
在這段代碼中,盡管數組覆蓋了9個字節,卻不會產生編譯錯誤;這是因為在編譯時,編譯器不知道變量X的值。
4、程序終止時存取違例
當正常關閉一個程序時發生存取違例,一般都說明堆棧設置的太小。盡管在32位程序中這種情況發生的可能性不大,但在極端情況下也會發生。就像前面講過的刪除一個已被刪除的指針,也可能引起程序終止時存取違例。
調試快速提示
除前面給出的許多提示外,還要補充一下提示:
- 改變窗體的Caption屬性,從而不必使用斷點就能顯示變量。因為在窗體上添加Label組件是件很容易的事情,因此用戶可使用Label組件,改變Label組件中的正文來顯示變量的值或其他要顯示的信息。
- 啟用一個條件斷點或數據監視斷點來臨時減慢程序的運行速度(比如要讓程序緩慢運行以便查看程序效果)。當程序運行碰到斷點時,要檢查斷點條件,從而減慢程序的執行。
- 在程序運行時,使用Evaluate/Modify對話框臨時改變一個變量的值。這樣,用戶可查看不同值對程序的影響,而不必每次重新編譯代碼。
- 從主菜單上選擇【Run | Inspect】,並在Expression字段輸入Self,來檢查調試器當前暫停處的類。
- 使用MessageBeep($FFFF)作為發生指示器,來指示程序執行到達程序中的某一位置。當用參數$FFFF時,它會使PC的揚聲器發出嘟嘟聲。
- 從主菜單上選擇【Run | Program Reset】菜單項或按【Ctrl + F2】鍵來終止出錯調試過程。
- 使用中間變量來斷開長等式或連鎖方法調用,以便用戶能更方便地檢查結果。
- 使用ShowMessage,MessageBox或MessageDlg函數來顯示程序跟蹤信息(ShowMessage函數用來更方便,因為它只有一個字符串參數)。
Caution
如果用戶在Windows 95上運行Delphi,則要盡量少使用Program Reset選項。有些時候,使用Program Reset終止應用程序的運行會破壞Windows 95。
但不是所有的Windows 95都會出現這種情況,因此可能不會遇到這個問題。Windows NT發生這種問題的可能性更小,因此在Windows NT中可以放心地使用Program Reset。就其個人而言,本人只在調試的應用程序被鎖死時才使用Program Reset。
還有一個提示就是,當調試程序時,可使用一個內存檢查程序。第三方的內存檢查程序用於檢查用戶應用程序有無內存泄露。當用戶將應用程序投入實際使用時,這類程序能省去許多麻煩。當應用程序存在內存泄露時,在使用該應用程序的過程中就會出問題。早起發現並排除內存泄露,無疑可大大節省用戶的時間和精力。