除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010...

16
除錯技巧 您將於本章學到以下各項: h 如何在 Visual C++ 2010 的除錯工具控制下執行程式? h 如何逐步地執行程式的敘述? h 如何監看或改變程式中的變數值? h 如何監看程式中計算式的值? h 何謂 Call Stack h 何謂診斷器(assertion )?如何使用診斷器來檢查程式碼? h 如何在程式中加入特殊的除錯碼? h 如何在原生 C++ 程式中偵測記憶體漏失的狀況? h 如何使用執行追蹤功能,在 C++ /CLI 程式中產生除錯輸出? 做完前一章的範例,您已經跟程式碼的 bugs (錯誤)搏鬥過了。本章將探究 Visual C++ 2010 所提供的基本除錯功能如何協助我們除錯;並且,也調查了許多其他的工 作,可協助由程式中找出錯誤並消除錯誤;此外,還有一些其他的方法,可將程式配 合上特殊的程式碼,以便檢查錯誤。

Transcript of 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010...

Page 1: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

除錯技巧

您將於本章學到以下各項:

如何在 Visual C++ 2010的除錯工具控制下執行程式?

如何逐步地執行程式的敘述?

如何監看或改變程式中的變數值?

如何監看程式中計算式的值?

何謂 Call Stack?

何謂診斷器(assertion)?如何使用診斷器來檢查程式碼?

如何在程式中加入特殊的除錯碼?

如何在原生 C++ 程式中偵測記憶體漏失的狀況?

如何使用執行追蹤功能,在 C++ /CLI程式中產生除錯輸出?

做完前一章的範例,您已經跟程式碼的 bugs(錯誤)搏鬥過了。本章將探究 Visual C++ 2010 所提供的基本除錯功能如何協助我們除錯;並且,也調查了許多其他的工作,可協助由程式中找出錯誤並消除錯誤;此外,還有一些其他的方法,可將程式配

合上特殊的程式碼,以便檢查錯誤。

Page 2: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 756

11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。現在,想必您一定知道除錯是程式設計過程中的一部分,而直接地面對程式的 bugs 將是相當沉悶的工作:

每個程式都有 bugs,必須試著將它找出來,並予以消除,這樣程式才能更可靠且有效率。請注意以下三項:程式的 bugs 不一定會出現;就算出現,也可能不知道它在原始程式碼的什麼地方;即使知道在哪裡,可能也很難決定是什麼

原因造成的,而因此消除它。

即使是已經完整地測試過的程式,許多程式仍然含有 bugs。

即使程式一直以來都能正確的執行,程式仍可能隱藏著 bugs。通常在最困難的時候,它們才會出現。

不論花了多少時間或努力去測試,複雜而且較大的程式,通常還是有 bugs。(這裡所衡量大小並無精準的定義,但 Visual C++ 2010和作業系統當然都算是複雜且較大的程式)

在最後一點上面打轉是很不明智的,試著別去想它。在正常電腦運算處理程序中,任

何錯誤事件的發生都會造成整體損害的。

在編譯和連結階段,可消除許多潛在的 bugs,但即使已經產生了可執行模組,程式仍可能有 bugs存在。很不幸的,儘管事實顯示 ── 程式 bugs是不可避免的,但除錯仍不是一門精準的技術。但是,您還是可以採用結構化的方法來消除錯誤。以下是除錯

時可採用的四大策略:

別重新發明輪子!了解並運用 Visual C++ 2010 函式庫所提供的函式(或其他的商業軟體元件),讓程式儘可能使用已經做過相當完整的測試的程式碼。

以漸進的方式發展並測試程式。個別地測試每個重要的類別和函式,測試後,再將各別的程式碼元件逐漸地組合起來,這樣的發展過程比較輕鬆,而且也較

少發生隱藏的 bugs。

程式碼的防護 ── 也就是撰寫程式碼來保護程式、防止潛在的錯誤。例如,將原生 C++ 類別的成員函式宣告為 const,將不會對物件內容變更。請在適當的地方使用 const參數。別在程式中使用「神奇數字」 ── 以需要的數值定義出會改變物件內容的 const物件。

從程式開始就加入檢查、驗證資料以及條件等除錯程式碼。在本章將會更詳細地討論這個方法。

Page 3: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 757

結束一個程式時,要儘可能地沒有 bugs,而且人性化。Visual C++ 2010 提供一群強大的工具來尋找 bugs。在進入更詳細的技術前,請先仔細地看看 bugs 是如何產生的。

程式 Bugs

當然,程式引起 bugs 的主要原因是您,以及您所造成的錯誤。這些錯誤的範圍從簡單的打字錯誤,到整個邏輯的錯誤,都可能發生。我也經常發現,很難相信自己竟然

會犯這樣愚蠢的錯誤。還沒有人能提出一項可靠的方案來應付程式中的 bugs。人類是創造的生物,所以,您將一次又一次地發現自己創造了許多錯誤。挫折的是,即使有

許多錯誤是那麼明顯,您還是看不到 ── 這就是您的電腦教導您要謙卑的方式。大體來說,有兩種錯誤的情形會導致程式產生 bugs:

語法錯誤(Syntactic errors) ── 有些錯誤情況是由不正確的敘述格式而來的;舉例來說,在敘述的結尾忘記加上分號,或者在需要逗號的地方使用冒

號。對於語法錯誤無須太過擔心。編譯器會找出所有的語法錯誤,並且提示錯

誤為何,所以還算很容易修正。

語義錯誤(Semantic errors) ── 此種錯誤發生時,程式的語法是正確的,不過所做的事情並非我們所期望的。對於程式來說,編譯器沒有辦法知道我們的

意圖,因此沒有辦法找到語義上的錯誤;然而,這種錯誤常常會造成程式意外

地終止,可以由一些徵兆看得出哪裡出了問題。在 Visual C++ 2010 中,其除錯功能主要針對語義錯誤為我們提供輔助。語義錯誤有可能相當難以捉模且不

易被找到;例如,程式會偶而地產生錯誤的結果或者是突然地當掉。這種最難

解決的 bugs 很有可能出現在多執行緒(multi-thread)的程式中,並且很有可能是因為不當平行處理之管理所造成的。

當然,有許多 bugs在系統環境中(包括 Visual C++ 2010在內),但當程式無法運作時,這是您最後可以懷疑的地方。即使當您下結論說:「一定」是編譯器或作業系統

的錯;實際上,十次有九次是您自己弄錯了。然而,在 Visual C++ 2010 裡仍有些bugs , 若 想 隨 時 更 新 為 最 新 的 版 本 , 可 以 在 Microsoft 的 web 站 上

(http://msdn2.microsoft.com/en-us/visualc/default.aspx)找到有關 Visual C++ 的資訊。更好的方法是,如果負擔得起訂閱的費用,可以訂閱 Microsoft Developer Network季刊,就能得到最新發現的 bugs資訊以及修正的更新內容。

若是可以為程式碼找到的 bugs 整理出一個清單的話,這樣對未來是相當有幫助的。以先前發生過的錯誤來檢查新撰寫的程式碼,常常可以減少新專案所需的除錯時間。

根據程式設計的性質,bugs 有無窮多種不同的形式,但某些類別是相當普遍的,您可以完全地了解這些 bugs,讓我們快速地瀏覽一下。

Page 4: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 758

一般的 bugs

將 bugs 分類的一個有效方法是根據它們引起的徵兆來分類,因為這通常是您第一次碰到這些 bugs 的經驗。以下清單中有五種一般的徵兆,但並未詳細說明,等您在撰寫程式的經驗中發現這些 bugs時,可加入其他的說明:

徵兆 可能引起的原因

資料損壞(Data corrupted) 變數初始化失敗 超過整數型態的範圍 不合法的指標 當做陣列索引的運算式錯誤 迴圈條件錯誤 動態配置的陣列大小錯誤 類別的拷貝建構函式、指定運算子或解構函式失敗

無法處理的例外情形(Unhandled exceptions)

不合法的指標或參考 缺少 catch處理

程式卡住或毀壞(Program hangs or crashes)

變數初始化失敗 無限迴圈 不合法的指標 重複釋放相同的 free store記憶體 類別的解構函式錯誤或失敗 使用者的輸入發生未預期的錯誤

輸入匯流的資料不正確(Stream input data incorrect)

使用額外的運算子或 getline() 函式讀取資料

不正確的原因(Incorrect results) 打字錯誤,例如 == 打成 :=,或 j打成 i 變數初始化失敗 超過整數型態的範圍 不合法的指標 忽略了 switch敘述中的 break

看看有多少不同種類的錯誤是由於不合法的指標所引起的,而不好的指標產生的徵兆

有無數種,這可能是最常引起那些難找的 bugs 的原因,所以最好反覆的檢查指標的操作。若能發現造成不好的指標的一些方法,或許就能避免掉許多產生 bugs 的陷阱。一般而言,造成不好的指標的原因如下:

Page 5: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 759

在宣告指標時,初始化失敗。

在釋放記憶體空間時,要讓原來指向 free store 記憶體的指標指向 null,卻失敗了。

從函式傳回區域變數的位址。

由 free store配置的類別,其拷貝建構函式和指定運算子失敗。

即使這些都避免了,還是可能有 bugs,所以,讓我們來看看 Visual C++ 2010提供了哪些協助除錯的工具。

11.2 基本的除錯運作 到目前為止,我們已建立了除錯版程式,但卻尚未使用到除錯器(debugger)。除錯器是一個程式,以逐行執行原始程式碼的方式來控制程式的執行,或執行到程式的某

個設定點為止。除錯器會在程式的每個設定點停頓,在繼續執行之前,可以檢查或修

改變數的值,可以改變原始程式碼,重新編譯並重新執行,也可以在除錯中修改程式

碼。當您修改程式碼並移到下一步時,除錯器會在執行下個敘述前自動重新編譯。

要了解 Visual C++ 2010 的基本除錯能力,就使用除錯器來執行一個程式,看看它是如何運作的。以下使用第 4章的一個使用指標的範例來實驗:

Page 6: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 760

若您的系統中已經有這個範例,打開專案即可;否則就得重新輸入範例內容。

當程式未依照它應作的行為來動作時,除錯器讓您能逐步地執行程式,以便檢查程式

的運作,找出哪裡發生了問題。它也允許您在程式的執行期間,檢查程式的資料狀

況。我們將逐步執行範例程式,並監看我們想看的變數內容。在此範例中,要監看

pnumber,以及 pnumber 所指的區域的內容(也就是 *pnumber),還有 number1 和number2。

首先,確定將此範例的程式建立的設定值(build configuration)設為 Win32 Debug而非 Win32 Release(內定值為 Win32 Release,所以一定要自行修改)。在選擇Project/Settings 選項後,將看到 build configuration 的設定選項。目前作用的 build configuration 顯示在旁邊之標準工具列(Standard toolbar)的下拉式列表(drop-down lists)中。要顯示或隱藏個別的工具列,只須以滑鼠右鍵點選工具列,並從列表對工具列做選取或撤除。請確認在使用 Debug 時,除錯工具列有正常顯示。除錯工具列會在除錯器運作時自動地出現,而在開始使用它以前,請先看看其中包含哪些項

目。由延伸的下拉式列表選取不一樣的設定值可以改變 build configuration 的設定。此外,也可以使用 Build Configuration Manager... 選單的選項來做設定的動作。

當您想要知道工具列按鈕是代表什麼時,可以將滑鼠游標移到按鈕上。此時按鈕的提

示說明將顯現出來,並說明該功能為何。

在專案中的 Debug 版本設定檔將導致額外的資訊被加入可執行程式檔,供除錯設施使用。額外的資訊將儲存在專案的 Debug 檔案夾之下的 .pdb 檔案中。當您在測試完整的程式中不希望有過多的描述時, 'release' 版本的設定檔會刪除這些除錯資訊。在專案版或企業版的 Visual C++ 2010中,編譯器在編譯 release版的程式時會做最佳化的處理;但 debug 版的程式在編譯時則不做最佳化處理,因為最佳化的過程牽涉到重新排列程式碼,或刪除重複的程式碼,以便讓程式更有效率。因為最佳化會破壞原始檔

和對應機器碼區段間的對應關係,當程式逐步執行時,可能造成混淆的情形發生。

檢視此工具列按鈕上的提示,將會對這些工具的功能有初步的概念。然而,只會簡單

地使用它們。第 4 章的範例並不需要使用所有的除錯設施,只會用到一些比較重要的工具,等熟悉如何用除錯器來逐步執行程式時,對於具有 bugs 的程式有哪些特徵,將做更深入的探討。

程式檔名:Ex4_05.cpp

Page 7: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 761

選擇 Debug Start Debugging選項或按下 F5,並點選 Debug工具列最左邊的按鈕都可以開啟除錯器。對於範例來說,建議使用工具列。除錯器有兩種主要的運作模式:

逐步執行程式碼(一次執行一個敘述是很重要的);或執行到原始程式的某個特殊的

設定點。除錯器在原始程式碼中停頓的設定點是由我們設定的,或另一種更有效的、

專門設計的停頓點,稱為中斷點(breakpoint)。首先,先看看中斷點的定義。

設定中斷點

中斷點(breakpoint)是除錯器在程式中會自動執行暫停的點。程式中可指定多個中斷點,讓程式在您感興趣的地方停頓。在每個中斷點都可觀看程式的變數,而且,若

變數的值不對,還可以去修改它;但這是不切實際的,因為修改之後,Ex4_05 程式的執行狀況已經不是您所期望的了。通常您只想看程式中某部分您認為可能有錯的特

別區域,因此,通常您會在您認為有錯的地方設定中斷點,而執行的程式就會停頓在

第一個中斷點。然後再從這個中斷點開始,逐步地執行,每次執行一行原始程式敘

述。

在原始程式的某行開端設定中斷點,只要在想要停止執行的敘述行數左邊,點選灰色

區塊即可。稱為雕版(glyph)的紅色圈圈會出現,代表此行是一個中斷點。若想要移除中斷點,請以滑鼠雙擊雕版。在圖 11-1 的編輯窗格裡,於 Ex4_05 設定了兩個中斷點。

圖 11-1

Page 8: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 762

在除錯時,通常會設定多個中斷點。當您認為可能引起問題的變數改變時,就在中斷

的時候顯示該變數。在指定中斷點的敘述之前,程式就會暫停執行。編譯器只會在一

行完整的敘述前中斷,並不會在敘述中間中斷。游標若放在一行的開端,則會在這行

敘述執行之前中斷;游標若放在沒有任何程式碼的地方(好比說圖 11-1 裡第二個中斷點上面那一行),則中斷點會設定在那一行,而程式將在下一行可執行的敘述執行

前停頓。

只要用滑鼠按鈕對紅點雙擊即可移除中斷點。另外也可以在包含中斷點的那一行點選

滑鼠右鍵,由快顯選單(pop-up)選取刪除中斷點的選項。若想要刪除使用中之專案的所有中斷點,可以選取 Debug Delete All Breakpoints 選項,或者是按下Ctrl+Shift+F9 即可。請注意!這會將專案中所有檔案的中斷點都刪除,包括目前未在Editor窗格打開的檔案在內。

進階的中斷點

當按下 Alt+F9,或者由 Debug 工具列點取最右邊的 Windows 按鈕,從列表選擇Breakpoints 後,出現的視窗提供了另一種指定中斷點的進階方式。這個視窗如圖 11-2所示。

圖 11-2

工具列上的 Columns 按鈕,讓我們可以在視窗中顯示更多的欄位。舉例來說,它可以選擇顯示中斷點處的原始碼檔案名稱或者是函式名稱。另外,也可以在到達某敘述的

執行時,顯示目前發生的事情。

要設定中斷點進一步的選項,請在 Breakpoints 視窗中,以滑鼠右鍵點選該中斷點那行的內容,並且由快顯選單加以選取。與將中斷點設定在非敘述的開頭一樣,可以在

某一個布林運算式為 true 的時候設定中斷點。這是一個相當強大的功能,不過也為程式增加可觀的資源使用,因為運算式會不斷地持續計算。也因如此,即使在效能好的

機器上,程式的執行效果會變的很緩慢。另外,也可以讓程式的執行在某個計數值到

達一給定之數值時加以中斷。這種功能對於迴圈內的程式碼相當好用,讓程式不會在

每個迴圈的反覆流程裡都中斷。當您對中斷點做了任何的設定,中斷點的雕版會在中

間出現 + 符號。

Page 9: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 763

設定追蹤點

追蹤點( tracepoint)是一種特殊的中斷點,它具有自訂的相關動作。若

要建立追蹤點,請在想要設定追蹤點

的地方按下滑鼠右鍵,從快顯選單中

選取 Breakpoint Insert Tracepoint選項。接著您會看到如圖 11-3 的對話盒。

如您所見,追蹤點的提供的動作包含

印出一個訊息以及(或者是)執行一

個巨集,並可在追蹤點選擇是要停止

或是繼續執行。在原始程式碼裡,若

選擇到了追蹤點仍繼續執行的話,那

麼該行會以紅色菱形的雕版加以標

示。對話盒裡的文字說明了如何指定要輸出的訊息。舉例來說,若要輸出目前函式的

名稱,以及 pnumber的數值,請在文字欄裡輸入以下的內容:

當執行到追蹤點時,此行所輸出的結果會顯示在 Visual Studio應用程式視窗的輸出窗格(Output pane)中。

當選取 Run a macro: 的核取方塊時,將能夠由可取得之標準巨集列表中選擇使用的巨集。

開始除錯

應用程式除錯有五種模式,請在 Debug 選單上加以選擇,如圖 11-4所示。

1. Start Debugging 選項(也可從 Debug 工具列上的按鈕找到)單純地執行程式直到第一個中斷點

就停止執行。此時,在檢查完所有想檢查的項目

之後,再選取相同的選項或工具列按鈕,則將繼

續執行到下一個中斷點。使用這項功能即可從程

式的一個中斷點移到另一個中斷點,每次執行停

止時,可以檢查重要的變數,若有需要則改變它

們的值。但若沒有設定中斷點,卻用這種方式來

啟動除錯器時,則會執行完整個程式而沒有任何

圖 11-3

圖 11-4

Page 10: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 764

暫停。當然,以這種方式開始除錯並不表示接下來一定要繼續使用這種模式,

每次暫停時都可以改選其他的任何一種除錯模式來繼續除錯。

2. Debug 選單中的 Attach to Process 選項,讓您能夠對一個正在執行的程序除錯。這個選項將顯示在您的機器上所執行的一連串程序,可以選擇想要進行除

錯的程序。這是專為進階的使用者設計的,而您應該避免試用它,除非知道自

己在做什麼。萬一干擾到作業系統中某些重要的程序,很可能讓機器卡死或引

起其他的問題。

3. Step Into 選項(也可從 Debug 工具列上的按鈕找到)會一次執行一個程式敘述,每個程式區塊都執行,包含裡面所呼叫的每個函式。若整個除錯過程都使

用這種模式就有點討厭,因為它也會執行匯流輸出的函式庫函式裡的每行程式

碼,但我們並不關心那些程式,因為那些不是我們寫的。有相當多的函式庫函

式是用組合語言寫的,包括許多支援輸入 / 輸出匯流的程式。組合語言函式每次執行一個機器指令,這將會浪費許多時間。

4. Step Over(也可從 Debug 工具列上的按鈕找到)會單純地執行程式裡的每一個敘述,一次一個,並執行所有匯流運作的程式碼(或在敘述中可能呼叫的其

他函式),但執行這些函式時並不會暫停。

另外,還有一個未出現在 Debug 選單上的第六種除錯模式。請在任一行程式碼按滑鼠右鍵,並從內文選單中選擇 Run to Cursor 選項。由它的名稱很容易知道此種模式所提供的功能 ── 程式會執行到游標所在的位置,然後中斷執行讓我們檢查或修改程式裡的變數。無論以哪種模式開始除錯,都可以由最接近的中斷點開始,以五種模式的

任一種繼續執行。

現在就來範例程式。若要使用 Step Into 選項,請點選適當的選項,或者工具列按鈕,或者是按下 F11 來開始執行程式。在短暫的暫停以後(假設您已經建立完成專案的 build),Visual C++ 2010會切換到除錯模式。

當除錯器開始作業時,Editor 視窗下面會出現兩個具有頁籤的視窗。無論哪一個視窗,都可以在任何時間選擇任一個頁籤,觀看需要的資訊。除錯器開始作業時,要顯

示哪些視窗都可以自訂。完整的視窗列表出現在 Debug Windows 選單的下拉式列表中。左邊的 Autos 視窗會顯示目前執行中之函式的自動變數目前數值。右邊的 Call Stack 視窗可以識別目前的函式呼叫,不過同一個視窗內的 Output 頁籤可能更有趣。在 Editor 窗格中,可以看到 main() 函式的左大括號以箭頭加以標示,代表程式目前執行的位置,如圖 11-5所示。

Page 11: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 765

圖 11-5

此外,還可以看到第 11 行的中斷點以及第 17 行的追蹤點。對此時來說,程式執行到的地方還不能觀看任何變數,因為目前變數都還不存在。在程式執行到變數的宣告以

前,都無法觀察或更改變數。

對於處理 I/O(輸入 / 輸出)的匯流函式來說,要避免一口氣完成其所有的程式碼執行,可以使用 Step Over功能,讓其執行到下一個中斷點中止。

檢查變數值

定義您想要檢查的變數,稱為變數的 setting a watch。在設定任何 watches 之前,應該先在程式中宣告變數。我們可用三次的 Step Over 來執行變數的宣告。使用 Step Over 選項、工具列圖示,或是按 F10 三次,之後,箭頭將指示在第 11 行的敘述開端:

現在若觀看 Autos 視窗,應該會看到圖 11-6 的內容(雖然 &number1 的值(是一個記憶體位址),在您的系統所出現的值跟書上的可能不同)。請注意!&number1 和pnumber並不相等,因為將 pnumber設定為 number1的位址的敘述(箭頭目前正指著這行),目前尚未執行。函式的第一行將 pnumber初始化為 null指標,所以它的內容是 0;若沒有將指標初始化,它會包含無效的值 ── 可能是上個程式殘留在某 4 個byte的記憶體裡面的值;當然,也可能是 0,但基本上是任意數。

Page 12: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 766

您可選擇 Debug Windows 選單,在視窗的左下角顯示以下五個頁籤:

Autos 頁籤顯示目前敘述及之前執行後所留下來的自動變數(換

句話說,在編輯視窗的箭頭之前

的敘述)。

Locals 頁籤顯示目前函式的區域變數值。一般來說,在追蹤程式時,新變數會進到這個範圍裡,然後離開定義的區域時,變數又超出這個範圍。就此範例的

狀況而言,此視窗會一直顯示 number1、number2 以及 pnumber 的值,因為這裡只有一個 main() 函式,也只有一個程式區塊。

Threads頁籤讓我們能夠在進階的應用程式中,檢查與控制執行緒(thread)。

Modules 頁籤會列出目前執行之程式碼模組的詳細資料。若應用程式發生異常錯誤,可以檢視此頁籤的 Address 欄位,確定發生問題的記憶體位址並加以比較,用以判斷哪個模組出了問題。

可以在 Watch1 頁籤加入想要觀察的變數。請在視窗點選其中的一欄,然後輸入變數名稱。除此之外,也可以觀察一個 C++ 運算式的數值,輸入的方式與變數是相同的。透過 Debug Windows Watch選項最多可以加上額外三個

Watch視窗。

您可能已注意到:在 Autos 視窗中,pnumber 名稱前有一個加號,這表示此變數還有其他資訊可顯示。例如,一個

陣列或指標或類別物件。就此範例的狀

況而言,可以按變數前的加號來展開指

標變數的其他資訊。按 F10 或是按pnumber前的 +,除錯器將顯示儲存在指標所存的記憶體位址的值,如圖 11-7所示。

Autos 視窗會自動地提供我們所有需要的資料,顯示記憶體位址以及儲存在該位址的數值。整數值可被顯示為十進制或十六進制,若要切換,只要在 Autos 頁籤的任何地方按右鍵,然後從快顯選單加以選擇即可。選擇 Locals頁籤可看到目前函式的區域變數。Visual C++ 2010的其他除錯設施中,還有其他檢查變數的方法。

圖 11-6

圖 11-7

Page 13: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 767

在編輯視窗中觀看變數

若要觀看單一變數的值,且該變數在目前的文字編輯視窗中可看到,則最簡單的觀看

方法是讓游標在變數上停留一會,之後會彈跳出一個提示,顯示目前的變數值。同樣

的方法也適用於較複雜的運算式,先將整個運算式反白,再將游標移到反白區上停留

一會,則又會彈跳出一個提示,顯示目前運算式的值。試著將運算式 *pnumber*10反白,將游標移到反白區上,運算式目前的計算結果就顯示出來了。請注意,若運算式

不完整的時候 ── 好比說反白的區域沒有把 pnumber 提領用的 * 加入,或者只是將*pnumber* 反白,那麼數值結果都不會出現。

改變變數的值

Watch 視窗也允許在觀看變數時,改變變數的值。當顯示的值很明顯地有問題時,可能是因為程式有 bugs,也可能因為尚未執行任何程式碼,此時可以將值加以修正。若設為正確的值,則程式還能再繼續下去,這樣一來,就可以做更多的測試,或找出更

多的 bugs。若涉及多次重複的迴圈時,例如 30000 次,則可將迴圈計算器設為29995,只要逐步執行後面幾次,並驗證迴圈能正確終止就夠了。按 30000 次的 F10真的會累死人!另外,還有一項很有用的功能是在執期間,故意將變數設為會引起錯

誤的值,這樣可以檢查程式中負責處理錯誤的程式碼,否則有些時候這些功能是沒有

機會測試的。

要在 Watch 視窗更改變數的值,只要在變數值顯示的地方按兩下,然後輸入新值即可。若要改變的變數是陣列的元素,必須按陣列名稱前的加號,展開整個陣列,然後

再更改元素的值。若要讓變數的值以十六進位的方法顯示,可以直接輸入十六進位的

數值,或在十進位數值之前加上 0n(零後面加上 n),例如輸入 A9 或是 0n169;若只輸入 169,則會解釋為十六進位的數值。很自然地,不管願意與否都應該小心地將新值丟到程式中。除非能確定地知道您所做的改變會造成什麼影響,否則,最後可能

因為許多奇怪的行為而結束程式,這未必能讓程式更接近能運作的狀態。

也許您會發現,在除錯模式下多執行一些範例(可以用前面幾章的範例來練習),對

您是很幫助的。這將使您對於除錯器在許多不同的狀況下的運作,有更完整的概念。

監看變數和運算式,對於找出程式的問題是相當有幫助的,但要尋找和摧毀 bugs 還有許多更有幫助的方式。接下來,讓我們看看如何在程式中加入某些程式碼,以提供

更多關於何時及為何會發生錯誤的資訊。

11.3 加入除錯程式碼 一個程式涉及了相當多的程式碼,所以一定要加入額外的程式碼來突顯可能的 bugs,並提供記錄輸出,協助找出 bugs 在哪裡。到底有哪些 bugs?哪一部分的程式可能有

Page 14: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 768

bugs?對於這些,在有任何想法之前,您不會想逐步地執行程式。當測試程式時,唯一需要的東西就是程式碼,一旦您覺得程式能夠完全地運作,就不再需要除錯程式碼

了,而且也不希望這些除錯碼在最後產生的程式版本執行時,造成多餘的負擔,或產

生某些不必顯示的輸出訊息。因此,額外的除錯程式碼只會在 debug(除錯)版的程式中運作,不會在真正 release版的程式中運作。

由 debug 版所產生的輸出,應該可以提供一些線索。例如:什麼會引起問題?而且,若程式作好了除錯程式的工作,它將會給您一些很好的意見,指示出程式的哪個部分

發生了錯誤。之後,就可以使用除錯器找出某種性質的 bugs 以及它的所在位址,然後修正它。第一種檢查程式行為的方法是由 C++ 的函式庫函式所提供的。

使用診斷器

標準函式庫標頭檔 cassert 宣告了一個 assert() 函式,當一個特別的前端處理代碼NDEBUG沒有被定義時,即可用它來檢查程式的邏輯條件。此函式的宣告如下:

函式的指定引數為檢查條件,但若特別的前端處理代碼 NDEBUG 被定義時,assert() 函式就會無效。代碼 NDEBUG會自動被定義在程式的 release版本中,但不會定義在debug 版本中;因此,在 debug 版本的程式中,診斷器會檢查它的引數,但在 release版本中,診斷器卻不做任何事。在 debug 版本中,若要關閉診斷器,可使用 #define假指令將 NDEBUG 加入程式裡。為了要達到它的效果,必須將 #define NDEBUG 放在 #include <cassert> 之前,如以下敘述所示:

若傳給 assert() 為引數的運算式不是 0(即為 true),則函式不做任何事;否則,就輸出診斷訊息。顯示的內容有:運算式執行失敗、原始檔名稱、原始檔發生錯誤的行

號。顯示完診斷訊息之後,assert() 會呼叫 abort() 來結束程式。以下是一個使用診斷器的函式範例:

Page 15: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Chapter 11 除錯技巧 769

呼叫 append() 函式,並以 null 指標作為引數,在我們機器上所產生的診斷說明如下:

診斷器也顯示一個訊息方塊,提供以下三種選項,

如圖 11-8所示:

選擇 Abort 按鈕則立刻結束程式,Retry 按鈕則啟動 Visual C++ 2010的除錯器,逐步地執行程式,找出更多為何診斷失敗的原因。原則上,Ignore 按鈕允許程式繼續執行而不管有沒有錯誤,但通常這

是不明智的選擇,因為它的執行結果應該不是您所

預期的。

任何一種邏輯運算式都可作為 assert() 的引數,可以比較數值、檢查指標、驗證物件型態,或任何有助於檢查程式是否能正確運作的方

式。當某些邏輯條件失敗時,產生一些訊息是有幫助的,但通常需要相當多的診斷才

能偵測到 bugs並修正它。讓我們看看怎樣才能加入更普遍性的診斷程式碼。

加入您自己的除錯程式碼

使用前端處理程式假指令,以便安排加入任何想要加入的程式碼,它只在會 debug 版本編譯和執行;除錯程式碼在 release 版本將被完全地省略,因此對測試程式的效率完全沒有影響。您可以不用 NDEBUG 代碼作為包含除錯程式碼的控制機制,因為此代碼是用來控制標準函式庫的 assert() 函式的運作,正如上一節所討論的。另一種更好、更正面的控制機制是使用另一個前端處理代碼_DEBUG,Visual C++ 會自動將它加在 debug版的程式裡,但不會加在 release版本裡。除錯時,只要使用 #ifdef/#endif這對前端處理程式假指令,加上測試用的 _DEBUG 代碼,將要編譯和執行的程式碼圍起來即可,如以下敘述所示。

在 #ifdef 和 #endif 間的程式碼,唯有_DEBUG 代碼被定義時才會編譯。這表示,一旦程式碼完全測試過了,即可從 debug 版的程式碼完整地清除所有除錯碼,產生release 版的程式。除錯程式碼可以做任何對各個除錯階段有益的事,從簡單的輸出一個訊息,到依序執行的記錄(例如每個函式被呼叫時都記錄下來),以避免額外的證

實計算和資料驗證,或呼叫別的函式來提供除錯輸出。

圖 11-8

Page 16: 除錯技巧 - epaper.gotop.com.twepaper.gotop.com.tw/pdf/ACL030900.pdf · 756 y Visual C++ 2010 教學手冊 11.1 了解除錯 Bugs 是程式中的錯誤,而除錯(debugging)就是找出並消除錯誤的過程。

Visual C++ 2010 教學手冊 770

當然,在原始程式檔中,可能有好幾個像這樣的除錯區塊,也可能使用自訂的前端處

理代碼來提供更多選擇性,決定包含哪些程式碼。這樣做的一個理由是:如果某些除

錯程式碼產生許多的輸出時,可以在真的需要時才產生這些輸出。另一種可提供更高

階,而且更詳細的除錯輸出,每個執行都可挑選想要產生的輸出,但即使有這些實

例,使用 _DEBUG 代碼來提供控制除錯碼,仍是一種很好的想法,因為可以確定的是 ── 它會自動讓 release程式中完全沒有除錯碼的負擔。

以下是一個簡單的例子。假設用兩個自訂的代碼來控制除錯碼 ── MYDEBUG 管理一般的除錯碼,VOLUMEDEBUG 控制產生許多輸出的除錯程式碼,而且這些輸出只在您想要產生時才產生。只有 _DEBUG被定義時才定義這些代碼:

要避免大量的除錯輸出,只要將 VOLUMEDEBUG定義變成註解,而不管有沒有定義_DEBUG 代碼。當程式有許多原始檔案時,您可能發現將除錯代碼集中在標頭檔是很方便的作法,之後只要在每個包含除錯碼的程式中 #include標頭檔即可。

接下來,介紹一個簡單的範例,看看實際上加入程式的除錯碼會如何運作。

TRY IT OUT 加入除錯的程式碼

為了探究這些除錯碼以及某些常用的除錯方法,以下利用一個簡單、且包含一些能被

找出並消除的 bugs 的程式範例。因此,您必須對本章其餘地方的所有程式碼有所懷疑,特別是當那個程式不需符合良好的程式設計習慣時。

要試驗除錯的運作,首先從定義類別著手,此類別表示一個人的名字;接著測試它的

動作。在此程式中有許多錯誤,所以請保持耐心去修正那些顯然有錯的程式碼 ── 這個想法是要讓您練習除錯運作,找出程式的錯誤。然而,實際上在執行程式時,有許

多的 bugs是非常明顯的,此時則不需使用除錯器或其他額外的程式碼來突顯它們。

請先建立一個 Win32 Console應用程式 Ex11_01,並且改變 Character Set專業特性為Not Set。接下來,再加入一個自訂的 Name.h 標頭檔,裡面包含 Name 類別的定義,Name 類別有兩個資料成員,是兩個指標,分別指向某人的姓和名。要宣告 Name 陣列物件時,除了內定建構函式之外,必須提供其他的建構函式。為了比較 Name 物件,所以在類別中加入多載運算子。此外,為了方便起見,必須可將完整的名稱視為

一個字串,直接從類別中取出,因此,位於 Name.h 檔案中的 Name 類別定義大致如以下敘述所示: