VC調試方法大全
一、調試基礎 調試快捷鍵 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++->常規(guī)->警告等級) 6、 _debug改成NDEBUG進行調試,project->setting->C/C++->Preprocessordefinitions(中文版是項目->屬性->C/C++->預處理器->預處理定義)(這里是debug和Release編譯的重要不同之一) 7、 在Release中調試源代碼,project->setting->C/C++->debug info選擇programDataBase(中文版是項目->屬性->C/C++->常規(guī)->調試信息格式->用于“編輯并繼續(xù)”的程序數據庫),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)(特別注意:不是執(zhí)行‘!’以前之所以不能看到TRACE內容,是因為不是調試執(zhí)行,而是‘!’了,切記,切記) 4.然后就會在OUTPUT中的DEBUG窗口中看到TRACE內容了,調試執(zhí)行會自動從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環(huán)境的輸出窗口(該窗口是你編譯項目出錯提示的哪個窗口),但僅限于你在VC中運行你的DEBUG版本的程序。 TRACE信息還可以使用DEBUGVIEW來捕獲到。這種情況下,你不能在VC的IDE環(huán)境中運行你的程序,而將BUILD好的DEBUG版本的程序單獨運行,這個時候可以在DEBUGVIEW的窗口看到DEBUGVIE格式的輸出了。 VC中TRACE的用法有以下四種: TRACE1 ,就是不帶動態(tài)參數輸出字符串, 類似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函數,使程序在運行過程中輸出一些調試信息,使我們能了解程序的一些狀態(tài)。但有一點不同的是: int x = 1; 要注意的是TRACE宏只對Debug 版本的工程產生作用,在Release 版本的工程中,TRACE宏將被忽略。 三、ASSERT宏 如果你設計了一個函數,該函數需要一個指向文檔對象的指針做參數,但是你卻錯誤地用一個視圖指針調用了這個函數。這個假的地址將導致視數據的破壞?,F在,這種類型的問題可以被完全避免,只要在該函數的開始處實現一個ASSERT測試,用來檢測該指針是否真正指向一個文檔對象。一般來講,編程者在每個函數的開始處均應例行公事地使用assertion。ASSERT宏將會判斷表達式,如果一個表達式為真,執(zhí)行將繼續(xù),否則,程序將顯示一條消息并且暫停,你可以選擇忽視這條錯誤并繼續(xù)、終止這個程序或者是跳到Debug器中。下面一例演示了如何使用一個ASSERT宏去驗證一個語句。 void foo(char p, int size ) { ASSERT( p != 0 ); //確認緩沖區(qū)的指針是有效的 ASSERT( ( size >= 100 ); //確認緩沖區(qū)至少有100個字節(jié) // 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循環(huán)將整個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卻不起任何作用??梢哉f,在Release模式下,ASSERT語句被刪除了。 請注意,如果你在一個ASSERT語句中錯誤地使用了一個能動的表達式,編譯器將不做任何警告地忽略它。在Release模式下,該表達式就會被無聲息地刪除掉,這將會導致程序的錯誤運行。由于Release版的程序通常不包含Debug信息,這類錯誤將很難被發(fā)現。 五、VC高級調試方法-條件及數據斷點的設定 (一)位置斷點(LocationBreakpoint) 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宏來輸出循環(huán)中的每一個結果,我們也可以在debug中看見輸出的結果,當出現問題時,輸出的結果可能就不一樣了,我們可以分析一下debug中的結果找出問題的所在 執(zhí)行此函數,程序崩潰于E行,發(fā)現此時tmp為0,假設tmp本不應該為0,怎么這個時候為0呢?所以最好能夠跟蹤此次循環(huán)時DoSome函數是如何運行的,但由于是在循環(huán)體內,如果在E行設置斷點,可能需要按F5(GO)許多次。這樣手要不停的按,很痛苦。使用VC6斷點修飾條件就可以輕易解決此問題。步驟如下。 Figure 1設置高級位置斷點 #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個字節(jié),然后呢,在0x0012ff70后面4個字節(jié)處,開始分配szName1這10個字節(jié),也就是在0x0012ff74處開始分配10個字節(jié), F10單步跟蹤,來到printf("%s\n", szName1)這一行,如下圖
szName1分配的空間已經附上了值. F10走到下一個printf("%s\n", szName1) 看下圖,
因為szName1 和szName2分配的空間是連續(xù)的,所以給szName2賦值超過所容納的字節(jié)時就開始覆蓋szName1的內容了,所以說當我們在輸出結果的時候就出現我們想不到的結果了, 那么怎么去調試呢,下面是具體的方法 szName1何時被修改呢?因為沒有明顯的修改szName1代碼。我們可以首先在A行設置普通斷點,F5運行程序,程序停在A行。然后我們再設置一個數據斷點。如下圖: Figure 2 數據斷點 (三) 其他 為了調試一個程序,首先必須使程序中包含調試信息。一般情況下,一個從AppWizard創(chuàng)建的工程中包含的Debug Configuration自動包含調試信息,但是是不是Debug版本并不是程序包含調試信息的決定因素,程序設計者可以在任意的Configuration中增加調試信息,包括Release版本。 為了增加調試信息,可以按照下述步驟進行:
打開Projectsettings對話框(可以通過快捷鍵ALT+F7打開,也可以通過IDE菜單Project/Settings打開) 選擇C/C++頁,Category中選擇general ,則出現一個Debug Info下拉列表框,可供選擇的調試信息 方式包括:
命令行 Project settings 說明 無 None 沒有調試信息 /Zd Line Numbers Only 目標文件或者可執(zhí)行文件中只包含全局和導出符號以及代碼行信息,不包含符號調試信息 /Z /Zi Program Database 創(chuàng)建一個程序庫(PDB),包括類型信息和符號調試信息。 /ZI Program Databasefor Edit and Continue 除了前面/Zi的功能外,這個選項允許對代碼進行調試過程中的修改和繼續(xù)執(zhí)行。這個選項同時使#pragma設置的優(yōu)化功能無效 選擇Link頁,選中復選框"Generate DebugInfo",這個選項將使連接器把調試信息寫進可執(zhí)行文件和DLL 如果C/C++頁中設置了Program Database以上的選項,則Link incrementally可以選擇。選中這個選項,將使程序可以在上一次編譯的基礎上被編譯(即增量編譯),而不必每次都從頭開始編譯。 |
|