在開發(fā)程序的過程中,經(jīng)常需要查找程序中的錯誤,這就需要利用調(diào)試工具來幫
助你進行程序的調(diào)試,當然目前有許多調(diào)試工具,而集成在VC中的調(diào)試工具以其
強大的功能,一定使你愛不釋手。下面我們先來介紹VC中的調(diào)試工具的使用。
1 VC調(diào)試工具 1.1 調(diào)試環(huán)境的建立 在VC中每當建立一個工程(Project)時,VC都會自動建立兩個版本:Release
版本,和Debug版本,正如其字面意思所說的,Release版本是當程序完成后,準備
發(fā)行時用來編譯的版本,而Debug版本是用在開發(fā)過程中進行調(diào)試時所用的版本。 DEBUG 版本當中,包含著MICROSOFT 格式的調(diào)試信息,不進行任何代碼優(yōu)化
,而在RELEASE 版本對可執(zhí)行程序的二進制代碼進行了優(yōu)化,但是其中不包含任
何的調(diào)試信息。 在新建立的工程中,你所看到是DEBUG版本,若要選擇RELEASE版本,可以選擇菜
單PROJECT中的SETTING命令,這時屏幕上面彈出PROJECT SETTEING 對話框,在
SETTING FOR下拉列表中選擇RELEASE,按OK退出,如圖4.1。
圖4.1 在調(diào)試程序的時候必須使用DEBUG版本,我們可以在Project Setting 對話框的
C/C++頁中設置調(diào)試選項。
圖4.2 各個選項的含意如下: · Program Database表示產(chǎn)生一個存儲程序信息的數(shù)據(jù)文件(.PDB),它包含
了類型信息和符號化的調(diào)試信息; · Line Numbers Only表示程序經(jīng)過編譯和鏈接產(chǎn)生的.OBJ或.EXE文件僅僅
包含全局和外部符號以及行號信息; · C7 Compatible表示產(chǎn)生一個.OBJ或.EXE文件行號信息以及符號化的調(diào)試
信息; · None表示不產(chǎn)生任何調(diào)試信息。
1.2調(diào)試的一般過程 調(diào)試,說到底就是在程序的運行過程的某一階段觀測程序的狀態(tài),而在一般情況
下程序是連續(xù)運行的,所以我們必須使程序在某一地點停下來。所以我們所做的
第一項工作就是設立斷點。其次,再運行程序,當程序在設立斷點處停下來時,再
利用各種工具觀察程序的狀態(tài)。程序在斷點停下來后,有時我們需要按我們的要
求控制程序的運行,以進一步觀測程序的流向,所以下面我們依次來介紹斷點的
設置,如何控制程序的運行以及各種觀察工具的利用。
1.3 如何設置斷點 在VC中,你可以設置多種類型的斷點,我們可以根據(jù)斷點起作用的方式把這些斷
點分為三類:1、與位置有關的斷點;2、與邏輯條件有關的斷點3、與WINDOWS消
息有關的斷點下面我們分別介紹這三類斷點。 首先我們介紹與位置有關的斷點。 1、 最簡單的是設置一般位置斷點,你只要把光標移到你要設斷點的位置,
當然這一行必須包含一條有效語句的;然后按工具條上的add/remove
breakpoint 按鈕或按快捷鍵F9;這時你將會在屏幕上看到在這一行的左邊出現(xiàn)
一個紅色的圓點表示這二設 立了一個斷點。 圖4.3
2 、有的時候你可能并不需要程序每次運行到這兒都停下來,而是在滿足一定條
件的情況下才停下來,這時你就需要設置一種與位置有關的邏輯斷點。要設置這
種斷點我們只需要從EDIT 菜單中選中breakpoint命令,這時Breakpoint對話框
將會出現(xiàn)在屏幕上。選中Breakpoint對話框中的LOCATION標簽,使LOCATION 頁
面彈出,如圖4.4
圖4.4 單擊condition按鈕,彈出Breakpoint對話框,在Expression編輯框中寫出你
的邏輯表達式,如X>=3或a+b>25,最后按OK返回。 圖4.5
這種斷點主要是由其位置發(fā)生作用的,但也結(jié)合了邏輯條件,使之更靈活。 3、有時我們需要更深入地調(diào)試程序,我們需要進入程序的匯編代碼,因此我們
需要在在匯編代碼上設立斷點:要設立這種斷點我們只需從View菜單中選Debug
window命令 ,
圖4.6 再選Disassembly子命令,這時匯編窗口將會出現(xiàn)在屏幕上。 圖4.7 在圖4.7中的匯編窗口中你將看到對應于源程序的匯編代碼,其中源程序是用黑
體字顯示,下面是且對應的匯編代碼。要設立斷點,我們只需將光標移到你想設
斷點處然后點擊工具條上的Insert/Remove Breakpoints 按鈕,此后你將會看到
一個紅圓點出現(xiàn)在該匯編代碼的右邊。
圖4.8 上面所講的斷點主要是由于其位置發(fā)揮作用的,即當程序運行到設立斷點的地方
時程序?qū)O聛?。但有時我們設立只與邏輯條件有關的斷點,而與位置無關。
所以下面介紹一下與邏輯條件有關的斷點。 (1)邏輯條件觸發(fā)斷點的設置: l 從EDIT 菜單中選中breakpoint命令,這時屏幕上將會出現(xiàn)Breakpoint對話
框。 圖4.9
l 選中Breakpoint對話框中的DATA標簽,對應的頁面將會彈出
圖4.10 l 在圖4.10的DATA頁面中的Expression編輯框中寫出你的邏輯表達式,如
(X==3); 圖4.11 l 最后按OK返回。 其他幾種斷點的設置的方法都與之類似。我們一一加以說明。 (2)監(jiān)視表達式發(fā)生變化斷點: l 從EDIT 菜單中選中breakpoint命令,這時屏幕上將會出現(xiàn)Breakpoint對話
框。 l 選中Breakpoint對話框中的DATA標簽,對應的頁面將會彈出 l 在Expression編輯框中寫出你需要監(jiān)視的表達式 l 最后按OK鍵返回。 (3)監(jiān)視數(shù)組發(fā)生變化的斷點: l 從EDIT 菜單中選中breakpoint命令,這時屏幕上將會 出現(xiàn)Breakpoint對
話框。 l 選中Breakpoint對話框中的DATA標簽,對應的頁面將會彈出 l 在Expression編輯框中寫出你需要監(jiān)視數(shù)組名; l 在Number of Elements 編輯框輸入你需要監(jiān)視數(shù)組元素的個數(shù); l 按OK鍵返回。 (4)監(jiān)視由指針指向的數(shù)組發(fā)生變化的斷點: l 從EDIT 菜單中選中breakpoint命令,這時在屏幕上將會出現(xiàn)Breakpoint對
話框。 l 選中Breakpoint對話框中的DATA標簽; l 在Expression編輯框中輸入形如*pointname,其中*pointname為指針變量名
; l 在Number of Elements 編輯框輸入你需要監(jiān)視數(shù)組元素的個數(shù); l 按OK鍵返回。 (5)監(jiān)視外部變量發(fā)生變化的斷點: l 從EDIT 菜單中選中breakpoint命令這時屏幕上將會出現(xiàn)Breakpoint對話框
; l 選中Breakpoint對話框中的DATA標簽; l 在Expression編輯框中輸入變量名; l 點擊在Expression編輯框的右邊的下拉鍵頭; l 選取Advanced選項,這時Advanced Breakpoint 對話框出現(xiàn); l 在context框中輸入對應的函數(shù)名和(如果需要的話)文件名; l 按OK鍵關閉Advanced Breakpoint 對話框。 l 按OK鍵關閉Breakpoints 對話框。 (6)在講了位置斷點和邏輯斷點之后我們再講一下與WINDOWS消息有關的斷點。 注意:此類斷點只能工作在x86 或 Pentium 系統(tǒng)上。 l 從EDIT 菜單中選中breakpoint命令,這時屏幕上將會出現(xiàn)Breakpoint對話
框; l 選中Breakpoint對話框中的MESSAGE標簽,對應的頁面將會彈出; l 在Break At WndProc 編輯框中輸入Windows 函數(shù)的名稱; l 在Set One Breakpoint From Each Message To Watch 下拉列表框中選擇
對應的消息; l 按OK 返回。
1.4 控制程序的運行 上面我們講了如何設置各類斷點,下面我們來介紹如何控制程序的運行。當我們
從菜單Build到子菜單Start Debuging 選擇Go 程序開始運行在Debug狀態(tài)下,程
序會由于斷點而停頓下來后,可以看到有一個小箭頭,它指向即將執(zhí)行的代碼。
圖4.12 隨后,我們就可以按要求來控制程序的運行:其中有四條命令:Step over,
step Into , Step Out ,Run to Cursor。
圖4.13 在圖4.13中: Step over 的功能是運行當前箭頭指向的代碼(只運行一條代碼)。 Step Into的功能是如果當前箭頭所指的代碼是一個函數(shù)的調(diào)用,則用Step Into
進入該函數(shù)進行單步執(zhí)行。 Step Out的功能是如當前箭頭所指向的代碼是在某一函數(shù)內(nèi),用它使程序運行至
函數(shù)返回處。 Run to Cursor的功能是使程序運行至光標所指的代碼處。 1.5 查看工具的使用 調(diào)試過程中最重要的是要觀察程序在運行過程中的狀態(tài),這樣我們才能找出
程序的錯誤之處。這里所說的狀態(tài)包括各變量的值,寄存中的值,內(nèi)存中的值,
堆棧中的值 ,為此我們需要利用各種工具來幫助我們察看程序的狀態(tài)。 ¨ 彈出式調(diào)試信息泡泡(Data Tips Pop_up Information)。 當程序在斷點停下來后,要觀察一個變量或表達式的值的最容易的方法是利用調(diào)
試信息泡泡。要看一個變量的值,只需在源程序窗口中,將鼠標放到該變量上,
你將會看到一個信息泡泡彈出,其中顯示出該變量的值。 圖4.14 要查看一個表達式的值,先選中該表達式,仍后將鼠標放到選中的表達式上,同
樣會看到一個信息泡泡彈出以顯示該表達式的值如圖4.15所示。
圖4.15 ¨ 變量窗口(VARIABLE WINDOW)。 在VIEW 菜單,Debug window選 Variables window; 變量窗口將出現(xiàn)在屏幕上。
其中顯示著變量名及其對應的值。你將會看到在變量觀察窗口的下部有三個標簽
:AUTO ,LOCAL,THIS 選中不同的標簽,不同類型的變量將會顯示在該窗口中。 圖4.16 ¨ 觀察窗口(WATCH WINDOW):
在VIEW 菜單,選擇Debug window 命令,Watch window 子命令。這時變量窗口
將出現(xiàn)在屏幕上。 圖4.17 在圖4.17的觀察窗口中雙擊Name欄的某一空行,輸入你要查看的變量名或表達式
。 圖4.18 回車后你將會看到對應的值。觀察窗口可有多頁,分別對應于標簽
Watch1,Watch2,Watch3等等。假如你輸入的表達式是一個結(jié)構(gòu)或是一個對象,你
可以用鼠標點取表達式右邊的形如 + ,以進一步觀察其中的成員變量的值如圖
4.19。
圖4.19 ¨ 快速查看變量對話框(quick watch); 在快速查看變量對話框中你可以象利用觀察窗口一樣來查看變量或表達式的值。
但我們還可以利用它來該變運行過程中的變量,具體操作如下: (1) 在Debug 菜單,選擇Quick Watch命令,這時屏幕上將會出現(xiàn)Quick
Watch 對話框;
圖4.20 (2) 在Expression 編輯框中輸入變量名,按回車;
圖4.21 (3)在Current Value 格子中將出現(xiàn)變量名及其當前對應的值如圖4.22:
圖4.22 (4)如要改變該變量的值只需雙擊該變量對應的Name 欄,輸入你要改變的
值; (5)如要把該變量加入到觀察窗口中,點擊Add watch 按鈕; (6)點擊Close 按鈕返回; ¨ 我們還可以直接查看內(nèi)存中的值 (1)從View菜單中選取Debug windows 及Memory 子命令。Memory Window 出現(xiàn)
;
圖4.23 (2)在Address 編輯框中輸入你要查看的內(nèi)存地址,回車。對應內(nèi)存地址中的
值將顯示在Memory window 的窗口中。
圖4.24 ¨ 在調(diào)試過程中,有時我們需要查看或改寄存器中的值。我們只需: (1)從View 菜單中選取Debug window 及 Registers 子選項。Registers
窗口出現(xiàn)。在Registers 窗口中,信息以 Register = Value 的形式顯示,其中
Register 代表寄存器的名字,Value 代表寄存器中的值。 圖4.25 (2)如果你要修改某一個寄存器的值,用TAB鍵,或鼠標將光標移到你想改
變的值的右邊,然后輸入你想要的值。回車返回。 在寄存器中,有一類特殊的寄存器稱為標志寄存器,其中有八個標志位: OV是溢出標志; UP是方向標志; EI是中斷使能標志; Sign 是符號標志, Zero是零標志。 Parity是奇偶較驗標志。 Carry 是進位標志。
2 高級調(diào)試技術(shù) 前面我們講了調(diào)試工具的使用,利用它可以就進行常規(guī)的調(diào)試,即使程序在某處
停下來,再觀察程序的當前壯態(tài)。而且這些工具在且它調(diào)試器中也有。但我們知
道我們知道在VC程序的開發(fā)過程中,光有這些工具是不夠的。為了更快更好地開
發(fā)程序,我們還需要利用更高級的調(diào)試工具。我們知道,在利用VC開發(fā)過程中,
利用MFC將會極大地方便應用程序的開發(fā),所以開發(fā)人員往往是利用MFC來開發(fā)應
用程序,正是這個原因Microsoft公司在MFC中提供了一些特性來幫助你進行程序
的調(diào)試。 我們知道在MFC中,絕大多數(shù)類都是從一個叫做Cobject的類繼承過來的,雖然這
是一個虛基類,但它定義了許多成員函數(shù),其中許多成員函數(shù)是用來支持程序的
調(diào)試的,如Dump ,Assertvalid 等成員函數(shù)。另外他們都支持如TRACE,ASSERT等
宏,并支持內(nèi)存漏洞的檢查等等。我們知道,為了支持調(diào)試,類庫肯定在在性能
上有所損失,為此Microsoft 公司提供了兩個不同的版本的類庫:Win32 Debug
版本和Win32 Release版本。在前面我們已經(jīng)提到,每當我們建立一個工程時,
我們也有對應的兩個版本。在你的DEBUG 版本的工程中,編譯器連接DEBUG 版本
的MFC類庫;在你的RELEASE 版本的工程中編譯器連接RELEASE版本的MFC 類庫以
獲得盡可能快的速度。下面我們來介紹這些工具的利用。
2.1 TRACE 宏的利用 TRACE 宏有點象我們以前在C語言中用的Printf函數(shù),使程序在運行過程中
輸出一些調(diào)試信息,使我們能了解程序的一些狀態(tài)。但有一點不同的是:TRACE
宏只有在調(diào)試狀態(tài)下才有所輸出,而以前用的Printf 函數(shù)在任何情況下都有輸
出。和Printf 函數(shù)一樣,TRACE函數(shù)可以接受多個參數(shù)如:
int x = 1; int y = 16; float z = 32.0; TRACE( "This is a TRACE statement\n" ); TRACE( "The value of x is %d\n", x ); TRACE( "x = %d and y = %d\n", x, y ); TRACE( "x = %d and y = %x and z = %f\n", x, y, z ); 要注意的是TRACE宏只對Debug 版本的工程產(chǎn)生作用,在Release 版本的工程中
,TRACE宏將被忽略。
2.2 ASSERT宏的利用 在開發(fā)過程中我們可以假設只要程序運行正確,某一條件肯定成立。如不成立
,那么我們可以斷言程序肯定出錯。在這種情況下我們可以利用ASSERT來設定斷
言。ASSERT宏的參數(shù)是一個邏輯表達式,在程序運行過程中,若該邏輯表達式為
真,則不會發(fā)生任何動作,若此表達式為假,系統(tǒng)將彈出一個對話框警告你,并
停止程序的執(zhí)行。同時要求你作出選擇:Abort,Ignore,Retry。若你選擇
Abort,系統(tǒng)將停止程序的執(zhí)行;若你選擇Ignore 系統(tǒng)將忽略該錯誤,并繼續(xù)執(zhí)
行程序;若你選擇Retry ,系統(tǒng)將重新計算該表達式,并激活調(diào)試器。同TRACE
宏一樣,ASSERT宏只DEBUG版本中起作用,在RELEASE版本中ASSERT宏將被忽略。
2.3 ASSERT_VALID宏的利用以及類的AssertValid()成員函的重載 ASSERT_VALID宏用來在運行時檢查一個對象的內(nèi)部合法性,比如說現(xiàn)在 有
一個學生對象,我們知道每個學生的年齡一定大于零,若年齡小于零,則該學生
對象肯定有問題。事實上,ASSERT_VALID宏就是轉(zhuǎn)化為對象的成員函數(shù)
AssertValid()的調(diào)用,只是這種方法更安全。它的參數(shù)是一個對象指針,通過
這個指針來調(diào)用它的AssertValid()成員函數(shù)。 與此相配套,每當我們創(chuàng)建從Cobject類繼承而來的一個新的類時,我們可以重
載該成員函數(shù),以執(zhí)行特定的合法性檢查。
2.4對象的DUMP函數(shù)的利用 Dump 函數(shù)用來按指定的格式輸出一個對象的成員變量,來幫助你診斷一個
對象的內(nèi)部情況。與AssertValid成員函數(shù)一樣,Dump也是Cobject 類的成員函
數(shù)。Dump函數(shù)的參數(shù)是一個CdumpContext對象,你可以象利用流一樣往向這個對
象中輸入數(shù)據(jù)。當你創(chuàng)建一個Cobject繼承而來的 新類時,你可以按如下步驟重
載你自己的Dump函數(shù): (1) 調(diào)用基類的Dump函數(shù),以輸出基類的內(nèi)容; (2) 向Cdumpcontest對象輸出該類的數(shù)據(jù). 例如,典型的Dump函數(shù)定義如下: #ifdef _DEBUG void CPerson::Dump( CDumpContext& dc ) const { // call base class function first CObject::Dump( dc );
// now do the stuff for our specific class dc << "last name: " << m_lastName << "\n" << "first name: " << m_firstName << "\n"; } #endif 你可能已經(jīng)注意到整個函數(shù)的定義都包含在#ifdef _DEBUG 和#endif中,這
使得Dump成員函數(shù)只在DEBUG版本中發(fā)生作用,而對RELEASE版本不發(fā)生作用。
3 內(nèi)存漏洞的檢查 也許你已經(jīng)知道,在C++和C語言中指針問題也就是內(nèi)存申請與釋放是一個令人頭
疼的事情,假如你申請了內(nèi)存,但沒有釋放,并且你的程序需要長時間地運行,
那么,系統(tǒng)的資源將逐漸減少,當系統(tǒng)的資源全部被用完時,系統(tǒng)將會崩潰。所
以在開發(fā)程序的過程中一定要保證資源的完全釋放。下面我們來介紹內(nèi)存漏洞的
檢查。 也許你會問,系統(tǒng)是怎樣支持內(nèi)存漏洞的檢查的?其實在你的Debug版本中所有
的有關內(nèi)存分配的函數(shù)都是被重載過的,具體過程是這樣的,當你的程序申請內(nèi)
存時,它首先調(diào)用一般的內(nèi)存分配函數(shù)分配一塊稍大的內(nèi)存塊。在這一內(nèi)存塊中
分為四個小塊:Heap Information, buffer , User memory block, buffer。第
一塊為有關堆的信息,比如,申請該內(nèi)存的地點(文件名,行號),此內(nèi)存塊的類
型(如整型,浮點,或某一類的對象)等等。第二塊是一個緩沖區(qū),用于截獲用戶
對其申請內(nèi)存使用越界的情況。第三塊是真正給用戶的內(nèi)存,返回的指針也是指
向這兒。第四塊也是一個緩沖區(qū),作用同第二塊。 當你申請的內(nèi)存均被記錄在案后,要檢查內(nèi)存漏洞就比較容易了,粗略地說,假
如你要檢查某一程序段是否有內(nèi)存漏洞,你只需在這一程序 段的開始要求系統(tǒng)
為你做一個內(nèi)存使用情況的映象,記錄下程序開始時的內(nèi)存使用情況,然后在程
序段的末尾再使系統(tǒng)為你做一次內(nèi)存映象,比較兩次映象,以檢查是否有沒釋放
的內(nèi)存,假如有未釋放的內(nèi)存,根據(jù)這一塊中有關分配情況的信息來告訴用戶在
那兒申請的內(nèi)存沒釋放。 具體地講檢查內(nèi)存漏洞需要以下幾個步驟: l 在你所檢測的程序段的開始處建立一個CmemoryState對象,調(diào)用其成員函
數(shù)Checkpoint,以取得當前內(nèi)存使用情況的快照; l 在你所檢測的程序段的末尾處再建立一個CmemoryState 對象,調(diào)用其成員
函數(shù)Checkpoint ,以取得當前內(nèi)存使用情況的快照; l 再建立第三個CmemoryState 對象,調(diào)用其成員函數(shù)Difference,把第一個
CmemoryState對象和第二個CmemeoryState對象作為其參數(shù).,如果兩次內(nèi)存快照
不相同,則該函數(shù)返回非零,說明此程序 段中有內(nèi)存漏洞。下面我們來看一個
典型的例子:
// Declare the variables needed #ifdef _DEBUG CMemoryState oldMemState, newMemState, diffMemState; OldMemState.Checkpoint(); #endif // do your memory allocations and deallocations... CString s = "This is a frame variable"; // the next object is a heap object CPerson* p = new CPerson( "Smith", "Alan", "581_0215" ); #ifdef _DEBUG newMemState.Checkpoint(); if( diffMemState.Difference( oldMemState, newMemState ) ) { TRACE( "Memory leaked!\n" ); } #endif
|