一、調試基礎
調試快捷鍵
F5:? 開始調試
Shift+F5: 停止調試
F10:?? 調試到下一句,這里是單步跟蹤?
F11:?? 調試到下一句,跟進函數內部
Shift+F11:??從當前函數中跳出
Ctrl+F10:??調試到光標所在位置
F9:?????? 設置(取消)斷點
Alt+F9:????高級斷點設置
跟蹤調試
1、?盡量使用快捷鍵時行調試
2、?觀察調試信息
3、?高級中斷設置
異常調試
重試->取消->調試
函數堆棧,用variables或者call stack 窗口
Release調試
1、?經常測試你的Debug和Release版本
2、?不要移除調試代碼,如用ASSERT, TRACE等。
3、?初始化變量,特別是全局變量,malloc的內存,new的內存
4、?當你移除某個資源時,確保你移除了所有跟這個資源相關的申明(主要是在resouce.h文中)
5、?使用3或者4級的警告級編譯你的代碼,并確保沒有警告,project->setting->c/c++->warninglevel(中文版是項目->屬性->C/C++->常規->警告等級)
6、?_debug改成NDEBUG進行調試,project->setting->C/C++->Preprocessordefinitions(中文版是項目->屬性->C/C++->預處理器->預處理定義)(這里是debug和Release編譯的重要不同之一)
7、?在Release中調試源代碼,project->setting->C/C++->debug info選擇programDataBase(中文版是項目->屬性->C/C++->常規->調試信息格式->用于“編輯并繼續”的程序數據庫),project->setting->link選上Generate debug info(中文版是項目->屬性->鏈接器->調試->生成調試信息)
8、?走讀代碼,特別關注堆棧和指針
二、TRACE宏
當選擇了Debug目標,并且afxTraceEnabled變量被置為TRUE時,TRACE宏也就隨之被激活了。但在程序的Release版本中,它們是被完全禁止的。下面是一個典型的TRACE語句:
????…
???????int nCount =9;
???????CString strDesc("total");
???????TRACE("Count =%d,Description =%s\n",nCount,strDesc);
???????…
?
可以看到,TRACE語句的工作方式有點像C語言中的printf語句,TRACE宏參數的個數是可變的,因此使用起來非常容易。如果查看MFC的源代碼,你根本找不到TRACE宏,而只能看到TRACE0、TRACE1、TRACE2和TRACE3宏,它們的參數分別為0、1、2、3。
個人總結:最近看網絡編程是碰到了TRACE語句,不知道在哪里輸出,查了一晚上資料也沒找出來,今天終于找到了,方法如下:
1.在MFC中加入TRACE語句
2.在TOOLS->MFCTRACER中選擇 “ENABLE TRACING”點擊OK
3.進行調試運行,GO(F5)(特別注意:不是執行‘!’以前之所以不能看到TRACE內容,是因為不是調試執行,而是‘!’了,切記,切記)?
4.然后就會在OUTPUT中的DEBUG窗口中看到TRACE內容了,調試執行會自動從BUILD窗口跳到DEBUG窗口,在那里就看到TRACE的內容了,^_^
以下是找的TRACE的詳細介紹:
?==============================
??????TRACE宏對于VC下程序調試來說是很有用的東西,有著類似printf的功能;該宏僅僅在程序的DEBUG版本中出現,當RELEASE的時候該宏就完全消失了,從而幫助你調式也在RELEASE的時候減少代碼量。
使用非常簡單,格式如下:
TRACE("DDDDDDDDDDD");
TRACE("wewe%d",333);
同樣還存在TRACE0,TRACE1,TRACE2。。。分別對應0,1,2。。個參數
TRACE信息輸出到VC IDE環境的輸出窗口(該窗口是你編譯項目出錯提示的哪個窗口),但僅限于你在VC中運行你的DEBUG版本的程序。
TRACE信息還可以使用DEBUGVIEW來捕獲到。這種情況下,你不能在VC的IDE環境中運行你的程序,而將BUILD好的DEBUG版本的程序單獨運行,這個時候可以在DEBUGVIEW的窗口看到DEBUGVIE格式的輸出了。
VC中TRACE的用法有以下四種:
TRACE1 ,就是不帶動態參數輸出字符串,??類似C的printf("輸出字符串");?
TRACE2:?中的字符串可以帶一個參數輸出?,類似C的printf("...%d",變量);
TRACE3:可以帶兩個參數輸出,類似C的printf("...%d...%f",變量1,變量2);
TRACE4 可以帶三個參數輸出,類似C的printf("...%d,%d,%d",變量1,變量2,變量3);
TRACE 宏有點象我們以前在C語言中用的Printf函數,使程序在運行過程中輸出一些調試信息,使我們能了解程序的一些狀態。但有一點不同的是:
TRACE 宏只有在調試狀態下才有所輸出,而以前用的Printf 函數在任何情況下都有輸出。和Printf 函數一樣,TRACE函數可以接受多個參數如:
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 版本的工程產生作用,在Release 版本的工程中,TRACE宏將被忽略。
三、ASSERT宏
如果你設計了一個函數,該函數需要一個指向文檔對象的指針做參數,但是你卻錯誤地用一個視圖指針調用了這個函數。這個假的地址將導致視數據的破壞。現在,這種類型的問題可以被完全避免,只要在該函數的開始處實現一個ASSERT測試,用來檢測該指針是否真正指向一個文檔對象。一般來講,編程者在每個函數的開始處均應例行公事地使用assertion。ASSERT宏將會判斷表達式,如果一個表達式為真,執行將繼續,否則,程序將顯示一條消息并且暫停,你可以選擇忽視這條錯誤并繼續、終止這個程序或者是跳到Debug器中。下面一例演示了如何使用一個ASSERT宏去驗證一個語句。
void foo(char p, int size )??
???????{
???? ?ASSERT( p != 0?); //確認緩沖區的指針是有效的
???? ASSERT( ( size >= 100? ); //確認緩沖區至少有100個字節
???????? // Do the foo calculation
}
這些語句不產生任何代碼,除非—DEBUG處理器標志被設置。Visual C++只在Debug版本設置這些標志,而在Release版本不定義這些標志。當—DEBUG被定義時,兩個assertions將產生如下代碼:
//ASSERT( p!= 0 );
??????do{
???? if( !(p !=0)?&& AfxAssertFailedLine(—FILE—,—LINE—) )
???????? AfxDebugBreak();
??????}while(0);
??????//ASSERT((size 〉= 100);
??????do{
???? if(!(size 〉= 100) &&AfxAssertFailedLine(—FILE—,—LINE—))
???????? AfxDebugBreak();
}while(0);
?
Do-while循環將整個assertion封裝在一個單獨的程序塊中,使得編譯器編譯起來很舒暢。If語句將求取表達式的值并且當結果為零時調用AfxAssertFailedLine()函數。這個函數將彈出一個對話框,其中提供三個選項“取消、重試或忽略”,當你選取“重試”時,它將返回TRUE。重試將導致對AfxDebugBreak()函數的調用,從而激活調試器。
AfxAssertFailedLine()是一個未正式公布的函數,它的功能就是顯示一個消息框。該函數的源代碼駐留在afxasert.cpp中。函數中的—FILE—和—LINE—語句是處理器標志,它們分別指定了源文件名和當前的行號。
?
AfxAssertFailedLine()是一個未正式公布的函數,它的功能就是顯示一個消息框。該函數的源代碼駐留在afxasert.cpp中。函數中的—FILE—和—LINE—語句是處理器標志,它們分別指定了源文件名和當前的行號。
?
四、VERIFY 宏
?
因為assertion只能在程序的Debug版本中起作用,在表達式中不可以包含賦值語句、增加語句(++)或者是減少語句(--),因為,這些語句實際改變數據。可有時你可能想要驗證一個能動的表達式,使用一個賦值語句。那么就到了用VERIFY宏來替代ASSERT。例如:
voidfoo(char p, int size )
???????{
???? char q;
??????????????VERIFY(q = p);
??????????????ASSERT((size 〉= 100);
???????????????//Do the foo calculation
???????????????//Do the foo calculation
???????}
?
在Debug模式下,ASSERT和VERIFY是一回事,但是在Release模式下,VERIFY宏仍然測試表達式而assertion卻不起任何作用。可以說,在Release模式下,ASSERT語句被刪除了。
?
請注意,如果你在一個ASSERT語句中錯誤地使用了一個能動的表達式,編譯器將不做任何警告地忽略它。在Release模式下,該表達式就會被無聲息地刪除掉,這將會導致程序的錯誤運行。由于Release版的程序通常不包含Debug信息,這類錯誤將很難被發現。
五、VC高級調試方法-條件及數據斷點的設定
(一)位置斷點(LocationBreakpoint)?
??大家最常用的斷點是普通的位置斷點,在源程序的某一行按F9就設置了一個位置斷點。但對于很多問題,這種樸素的斷點作用有限。譬如下面這段代碼:
void CForDebugDlg::OnOK()
{
?????? for(int i = 0; i < 1000; i++)??? //A
?????? {
????????????? intk = i * 10 - 2; //B
????????????? SendTo(k);????????? //C
????????????? inttmp = DoSome(i); //D
????????????? Trace0("這里要輸出的內容”);//在這里可以輸出一些有用的信息,你也可以輸出I的值,都是可以的
????????????? intj = i / tmp;??? //E
?????? }
}?????
?? //其實我們還可以用其他方法調式也是一樣的,你可以用TRACE0宏來輸出循環中的每一個結果,我們也可以在debug中看見輸出的結果,當出現問題時,輸出的結果可能就不一樣了,我們可以分析一下debug中的結果找出問題的所在
? ?? 執行此函數,程序崩潰于E行,發現此時tmp為0,假設tmp本不應該為0,怎么這個時候為0呢?所以最好能夠跟蹤此次循環時DoSome函數是如何運行的,但由于是在循環體內,如果在E行設置斷點,可能需要按F5(GO)許多次。這樣手要不停的按,很痛苦。使用VC6斷點修飾條件就可以輕易解決此問題。步驟如下。?
??1 Ctrl+B打開斷點設置框,如下圖:
?
Figure 1設置高級位置斷點 ?
??2 然后選擇D行所在的斷點,然后點擊condition按鈕,在彈出對話框的最下面一個編輯框中輸入一個很大數目,具體視應用而定,這里1000就夠了。?
??3 按F5重新運行程序,程序中斷。Ctrl+B打開斷點框,發現此斷點后跟隨一串說明:...487 times remaining。意思是還剩下487次沒有執行,那就是說執行到513(1000-487)次時候出錯的。因此,我們按步驟2所講,更改此斷點的skip次數,將1000改為513。?
??4 再次重新運行程序,程序執行了513次循環,然后自動停在斷點處。這時,我們就可以仔細查看DoSome是如何返回0的。這樣,你就避免了手指的痛苦,節省了時間。?
??再看位置斷點其他修飾條件。如Figure 1所示,在“Enter the expression to be evaluated:”下面,可以輸入一些條件,當這些條件滿足時,斷點才啟動。譬如,剛才的程序,我們需要i為100時程序停下來,我們就可以輸入在編輯框中輸入“i==100”。?
??另外,如果在此編輯框中如果只輸入變量名稱,則變量發生改變時,斷點才會啟動。這對檢測一個變量何時被修改很方便,特別對一些大程序。?
??用好位置斷點的修飾條件,可以大大方便解決某些問題。?
(二) 數據斷點(DataBreakpoint)?
??軟件調試過程中,有時會發現一些數據會莫名其妙的被修改掉(如一些數組的越界寫導致覆蓋了另外的變量),找出何處代碼導致這塊內存被更改是一件棘手的事情(如果沒有調試器的幫助)。恰當運用數據斷點可以快速幫你定位何時何處這個數據被修改。譬如下面一段程序:
#include "stdafx.h"
#include <string.h>
int main(int argc, char* argv[])
{
?????? charszName1[10];
?????? charszName2[4];
?????? strcpy(szName1,"shenzhen");?????????????
?????? printf("%s\n",szName1);????????? //A
?
?????? strcpy(szName2,"vckbase");????????????? //B
?????? printf("%s\n",szName1);
?????? printf("%s\n",szName2);
?????? return0;
}
??這段程序的輸出是
??????? szName1: shenzhen
?????? szName1:ase
?????? szName2:vckbase
??? 首先我給你分析一下為什么會是這樣的結果呢!首先你在strcpy(szName1,"shenzhen");這個地方F9設置一個斷點,然后F5運行程序,這是程序會斷到我們設置的斷點,如下圖
??
看到了吧,問題出現的原因就在這里,系統給szName2分配的地址是0x0012ff70這里是4個字節,然后呢,在0x0012ff70后面4個字節處,開始分配szName1這10個字節,也就是在0x0012ff74處開始分配10個字節,
?????? F10單步跟蹤,來到printf("%s\n", szName1)這一行,如下圖
?
szName1分配的空間已經附上了值.
?? F10走到下一個printf("%s\n", szName1) 看下圖,
?
因為szName1 和szName2分配的空間是連續的,所以給szName2賦值超過所容納的字節時就開始覆蓋szName1的內容了,所以說當我們在輸出結果的時候就出現我們想不到的結果了,
?那么怎么去調試呢,下面是具體的方法
szName1何時被修改呢?因為沒有明顯的修改szName1代碼。我們可以首先在A行設置普通斷點,F5運行程序,程序停在A行。然后我們再設置一個數據斷點。如下圖:
?
Figure 2 數據斷點?
??F5繼續運行,程序停在B行,說明B處代碼修改了szName1。B處明明沒有修改szName1呀?但調試器指明是這一行,一般不會錯,所以還是靜下心來看看程序,哦,你發現了:szName2只有4個字節,而strcpy了7個字節,所以覆寫了szName1。?
??數據斷點不只是對變量改變有效,還可以設置變量是否等于某個值。譬如,你可以將Figure 2中紅圈處改為條件”szName2[0]==''''y''''“,那么當szName2第一個字符為y時斷點就會啟動。?
??可以看出,數據斷點相對位置斷點一個很大的區別是不用明確指明在哪一行代碼設置斷點。
(三) 其他?
??1 在call stack窗口中設置斷點,選擇某個函數,按F9設置一個斷點。這樣可以從深層次的函數調用中迅速返回到需要的函數。?
??2 Set Next StateMent命令(debug過程中,右鍵菜單中的命令)?
??此命令的作用是將程序的指令指針(EIP)指向不同的代碼行。譬如,你正在調試上面那段代碼,運行在A行,但你不愿意運行B行和C行代碼,這時,你就可以在D行,右鍵,然后“Set Next StateMent”。調試器就不會執行B、C行。只要在同一函數內,此指令就可以隨意跳前或跳后執行。靈活使用此功能可以大量節省調試時間。?
??3 watch窗口?
??watch窗口支持豐富的數據格式化功能。如輸入0x65,u,則在右欄顯示101。?
??實時顯示windows API調用的錯誤:在左欄輸入@err,hr。?
??在watch窗口中調用函數。提醒一下,調用完函數后馬上在watch窗口中清除它,否則,單步調試時每一步調試器都會調用此函數。?
??4 messages斷點不怎么實用。基本上可以用前面講述的斷點代替。?
六。VC調試環境設置
為了調試一個程序,首先必須使程序中包含調試信息。一般情況下,一個從AppWizard創建的工程中包含的Debug Configuration自動包含調試信息,但是是不是Debug版本并不是程序包含調試信息的決定因素,程序設計者可以在任意的Configuration中增加調試信息,包括Release版本。
為了增加調試信息,可以按照下述步驟進行:
?
打開Projectsettings對話框(可以通過快捷鍵ALT+F7打開,也可以通過IDE菜單Project/Settings打開)
選擇C/C++頁,Category中選擇general ,則出現一個Debug Info下拉列表框,可供選擇的調試信息 方式包括:
?
?
命令行????? Project settings???????? 說明
無???????? None???????????????? 沒有調試信息
/Zd???????? Line Numbers Only???? 目標文件或者可執行文件中只包含全局和導出符號以及代碼行信息,不包含符號調試信息
/Z7???????? C7.0- Compatible????? 目標文件或者可執行文件中包含行號和所有符號調試信息,包括變量名及類型,函數及原型等
/Zi??????? ??Program Database????? 創建一個程序庫(PDB),包括類型信息和符號調試信息。
/ZI????????? Program Databasefor
?Edit and Continue?????? 除了前面/Zi的功能外,這個選項允許對代碼進行調試過程中的修改和繼續執行。這個選項同時使#pragma設置的優化功能無效
選擇Link頁,選中復選框"Generate DebugInfo",這個選項將使連接器把調試信息寫進可執行文件和DLL
如果C/C++頁中設置了Program Database以上的選項,則Link incrementally可以選擇。選中這個選項,將使程序可以在上一次編譯的基礎上被編譯(即增量編譯),而不必每次都從頭開始編譯。