在VC程序中使用調(diào)試語句 為了更好地對程序調(diào)試,可以使用如下方法:使用斷言,、使用跟蹤語句,、使用異常和返回值。 一,、斷言 1,、基本概念 斷言是一種讓錯(cuò)誤在運(yùn)行時(shí)候自我暴露的簡單有效實(shí)用的技術(shù)。它們幫助你較早較輕易地發(fā)現(xiàn)錯(cuò)誤,,使得整個(gè)調(diào)試過程效率更高,。 斷言是布爾調(diào)試語句,用來檢測在程序正常運(yùn)行的時(shí)候某一個(gè)條件的值是否總為真,,它能讓錯(cuò)誤在運(yùn)行時(shí)刻暴露在程序員面前,。使用斷言的最大好處在于,能在更解決錯(cuò)誤的發(fā)源地的地方發(fā)現(xiàn)錯(cuò)誤,。斷言具有以下特征: .斷言是用來發(fā)現(xiàn)運(yùn)行時(shí)刻錯(cuò)誤的,,發(fā)現(xiàn)的錯(cuò)誤是關(guān)于程序?qū)崿F(xiàn)方面的。 .斷言中的布爾表達(dá)式顯示的是某個(gè)對象或者狀態(tài)的有效性而不是正確性,。 .斷言在條件編譯后只存在于調(diào)試版本中,,而不是發(fā)布版本里。 .斷言不能包含程序代碼,。 .斷言是為了給程序員而不是用戶提供信息,。 使用斷言最根本的好處是自動(dòng)發(fā)現(xiàn)許多運(yùn)行時(shí)產(chǎn)生的錯(cuò)誤,但斷言不能發(fā)現(xiàn)所有錯(cuò)誤,。斷言檢查的是程序的有效性而不是正確性,,可通過斷言把錯(cuò)誤限制在一個(gè)有限的范圍內(nèi)。當(dāng)斷言為假,激活調(diào)試器顯示出錯(cuò)代碼時(shí),,可用Call Stack命令,,通過檢查棧里的調(diào)用上下文、少量相關(guān)參數(shù)的值以及輸出窗口中Debug表的內(nèi)容,,通常能檢查出導(dǎo)致斷言失敗的原因。_ASSERTE宏(屬于C運(yùn)行時(shí)間庫)還能在斷言失敗時(shí)顯示出失效斷言,。下面我們討論一下MFC庫中的斷言,。 2、MFC庫中的斷言 (1) ASSERT(布爾表達(dá)式) 用MFC時(shí)最好選擇ASSERT宏,,它的優(yōu)點(diǎn)是即使出現(xiàn)了WM_QUIT消息也能顯示斷言失效消息框,。 (2) VERIFY(布爾表達(dá)式) VERIFY宏中的布爾表達(dá)式在發(fā)布版本中被保留下來。VERIFY宏簡化了對函數(shù)返回值的檢查,,一般用來檢查Windows API的返回值,。由于VERIFY宏里的布爾表達(dá)式在發(fā)布版本里保留了下來,因此最好盡量不要使用這個(gè)宏以實(shí)現(xiàn)程序代碼和調(diào)試代碼的完全分離,。 (3 )ASSERT_VALID(指向CObject派生類對象的指針) ASSERT_VALID宏通過調(diào)用重載的AssertValid函數(shù)來確定指向CObject派生類對象的指針是否有效,。無論你什么時(shí)候從CObject派生類中得到一個(gè)對象,在對這個(gè)對象做任何操作之前都應(yīng)該調(diào)用ASSERT_VALID宏,。 (4) ASSERT_KINDOF(類名, 指向CObject派生類對象的指針) 這個(gè)宏用來驗(yàn)證指向CObject派生類對象的指針是否從某個(gè)特殊類中派生,,在調(diào)用它之前先調(diào)用ASSERT_VALID宏。只有在很特殊的場合下才用得到,,如檢測編譯器可能錯(cuò)過的對象類型問題,。 此外,還有兩個(gè)沒有正式文件的ASSERT宏的變種:ASSERT_POINTER(指針,指針類型),,ASSERT_NULL_OR_POINTER(指針,指針類型),。 3、什么時(shí)候使用斷言 把斷言看作一種簡單的制造柵欄的方法,,這種柵欄能使錯(cuò)誤在穿過自己時(shí)暴露,。 .檢查函數(shù)的輸入 .檢查函數(shù)的輸出 .檢查對象的當(dāng)前狀態(tài) .堅(jiān)持邏輯變量的合理性和一致性 .檢查類中的不變量 公有成員函數(shù)比私有和保護(hù)的成員函數(shù)需要更全面的斷言。 不正確地使用斷言會導(dǎo)致錯(cuò)誤,。斷言應(yīng)該檢測那些在程序正常運(yùn)行的時(shí)候永遠(yuǎn)都不可能出現(xiàn)的狀態(tài),。斷言是用來揭示錯(cuò)誤的,而不是用來糾正運(yùn)行時(shí)刻錯(cuò)誤的,。 4,、斷言與防御性編程(Defensive Programming) 斷言在調(diào)試的時(shí)候向程序員揭示運(yùn)行時(shí)刻錯(cuò)誤(調(diào)試版本里),而防御性編程使用戶在運(yùn)行程序(發(fā)布版本里)時(shí),,當(dāng)出現(xiàn)意外情況時(shí)程序仍能繼續(xù)工作,。實(shí)際上,防御性的編程要求程序在檢測到意外時(shí)返回一個(gè)“安全”的值(比如布爾函數(shù)返回false,指針和句柄返回空值),,一個(gè)錯(cuò)誤代碼或者拋出一個(gè)異常來解決問題,。特定的防御性編程技術(shù)包括:處理無效函數(shù)參數(shù)和數(shù)據(jù)、出現(xiàn)問題的時(shí)候程序失敗,、檢查臨界函數(shù)返回的錯(cuò)誤代碼以及處理異常,。需要防御性編程的標(biāo)準(zhǔn)問題包括:錯(cuò)誤的輸入數(shù)據(jù)、內(nèi)存或者硬盤空間不夠,、不能打開一個(gè)文件,、外部設(shè)備不能訪問、網(wǎng)絡(luò)連接不上或者甚至在程序中還有錯(cuò)誤,,目的是保持程序的運(yùn)行狀態(tài),。如果你的程序是防御性的,別忘了使用斷言,。如果你使用斷言,,也別忘了防御性編程。這兩種技術(shù)最好結(jié)合在一起使用,。 二,、跟蹤語句 1、基本概念 跟蹤語句(trace statements)可使程序執(zhí)行,,并使程序員可對可變值進(jìn)行查看,。它們提供了一個(gè)用于觀察的程序,并且獨(dú)立于一個(gè)交互式的調(diào)試器,,但是最具有特色的是它們常用于對調(diào)試器提供的信息進(jìn)行補(bǔ)充,。在VC中,跟蹤消息通常輸出到輸出窗口中的Debug標(biāo)簽,,也可以重新輸出到一個(gè)文件中,。跟蹤語句的特性如下: .跟蹤語句用于報(bào)告代碼中重要的運(yùn)行事件。 .跟蹤語句的編譯通常是有條件的,,并只存在于調(diào)試版本中,,而在發(fā)布版本中不被編譯。 .跟蹤語句不能包含程序代碼或?qū)Τ绦虼a有間接的影響作用,。 .跟蹤語句的目的是向程序員提供信息,,而不是向用戶。 跟蹤語句也是調(diào)試語句,,它可以執(zhí)行程序,,并且在運(yùn)行中程序員可以查看變量。跟蹤語句對于那些使用交互式調(diào)試器很難調(diào)試的程序是很有效的,。 跟蹤語句和斷言的區(qū)別如下: .跟蹤語句是無條件的,,斷言是有條件的布爾語句,。 .跟蹤語句用于顯示程序執(zhí)行和變量值,不直接顯示bug,,斷言用于顯示出bug,。 .跟蹤語句將信息輸出到調(diào)試窗口或文件中,可被隨意地忽略,,斷言打斷程序的執(zhí)行,。 2、MFC中的跟蹤語句 在MFC中,,你可以使用TRACE和AfxOutputDebugString宏,、CObject::Dump虛擬函數(shù)和AfxDumpStack函數(shù)。TRACE宏由AfxDump實(shí)現(xiàn),,AfxDump由AfxOutputDebugString實(shí)現(xiàn)。AfxOutputDebugString宏和AfxDumpStack函數(shù)可以在所有版本中編譯,,其他只能在調(diào)試版本中編譯,。 (1)TRACE宏有以下形式: _TRACE(reportType,format); _TRACE0(reportType,format,arg1); _TRACE1(reportType,format,arg1,arg2); _TRACE2(reportType,format,arg1,arg2,arg3); _TRACE3(reportType,format,arg1,arg2,arg3,arg4); 在MFC中,推薦使用TRACEn宏,,當(dāng)使用TRACE宏時(shí)需要使用_T宏來格式化參數(shù)以正確解決Unicode的校正,,而TRACEn不需要。 MFC TRACE宏中的一個(gè)缺點(diǎn)是AfxTrace函數(shù)使用一個(gè)512字符固定大小的緩沖區(qū),,這使得它在跟蹤長字符串時(shí)是無用的,。 (2)CObject::Dump CObject類有一個(gè)轉(zhuǎn)儲(dump)虛擬函數(shù),所有繼承CObject的類都可以通過重載這個(gè)函數(shù),,輸出它們的值,。 3、Visual C++消息Pragma 消息Pragma實(shí)際上是一個(gè)編譯時(shí)的跟蹤語句,,你可以使用它來警告在預(yù)處理過程中發(fā)現(xiàn)的潛在的編連(build)問題,。典型的例子: #if (WINVER>=0x0500) #pragma message (“NOTE:WINVER has been defined as 0x0500 or greater.”) #endif 消息Pragma是非常有用的,尤其是在復(fù)雜編連中,。然而,,如果你要檢測一種特定的問題,而不是潛在的問題,,使用#error預(yù)處理來代替打斷編譯會更直接一些,。 每當(dāng)你的程序中有錯(cuò)誤而你想得到更多信息的時(shí)候,你應(yīng)該去查看一下跟蹤消息,。由于VC輸出窗口的緩沖區(qū)是有大小限制的,,因此如果跟蹤消息數(shù)據(jù)產(chǎn)生的速度超過輸出窗口處理的速度,那么消息會塞滿緩沖區(qū),,導(dǎo)致數(shù)據(jù)丟失,。避免這個(gè)問題的簡單方法是在輸出大量數(shù)據(jù)的代碼段如轉(zhuǎn)儲對象時(shí),調(diào)用Sleep API函數(shù)。 三,、異常 1,、基本概念 錯(cuò)誤是一種條件,在這種條件下,,如果不執(zhí)行額外的處理,,線程就不能正常地執(zhí)行下去。異常是用于處理錯(cuò)誤的,。使用異常的一個(gè)很明顯的好處就是它們通過發(fā)出錯(cuò)誤信號,,可以讓程序代碼和錯(cuò)誤處理代碼分開,而且不會讓程序忽略錯(cuò)誤,,你不用不斷地檢查函數(shù)的返回值,,因此它們將程序代碼簡單化。另一個(gè)好處是它們不需要嚴(yán)格的編程作風(fēng),。 異常的基本特性: .異常是基于每個(gè)進(jìn)程而提出并處理的,。 .異常不能被線程忽略,必須被處理,。 .未處理的異常會使進(jìn)程結(jié)束,,而不僅僅是結(jié)束線程。 .異常出來在釋放棧時(shí)會釋放所有的棧對象,,避免了資源的漏洞,。 .異常處理需要大量的額外操作,使得它不適于經(jīng)常運(yùn)行的代碼,。 .可以拋出任何類型的異常對象,,除了整數(shù)。 如果正確執(zhí)行,,異常處理有下面的特性: .異常是不是正常的運(yùn)行結(jié)果,,是特殊情況。 .異常在返回值無效的情況下使用,。 .異常是可靠的,,不可能被忽略。 .異常簡化了錯(cuò)誤處理,,簡化了程序代碼,,使錯(cuò)誤處理更加方便。 Visual C++的默認(rèn)情況下,,在調(diào)試版本中處理異常,,而在發(fā)布版本中并不進(jìn)行處理。由于異常也是錯(cuò)誤,,Windows異常碼采用了同Windows錯(cuò)誤碼一樣的位映射模式,,為一個(gè)32位的值,,這些碼由Microsoft定義,任何異常碼的最高四位總是1100(二進(jìn)制),,即十六進(jìn)制里的0xC,。 2、Windows結(jié)構(gòu)異常和C++異常 Windows結(jié)構(gòu)異常作為硬件異常(如訪問非法或被零除)或操作系統(tǒng)異常的結(jié)果被拋出,,C++異常只能由throw語句拋出,。Windows結(jié)構(gòu)異常處理不能處理對象的解析,因此你應(yīng)該在C++程序中一直使用C++異常,。然而,,C++異常不能處理硬件和操作系統(tǒng)異常,你的程序需要將結(jié)構(gòu)異常轉(zhuǎn)化為C++異常,。C++異常并不直接從你的程序代碼中拋出而是從C++運(yùn)行庫中拋出,,因此你需要調(diào)用棧窗口來返回你的代碼。為了正確處理硬件和操作系統(tǒng)異常,,你可以創(chuàng)建自己的異常類并使用_set_se_translator函數(shù)安裝一個(gè)結(jié)構(gòu)異常向C++異常的轉(zhuǎn)化器,,但不要捕獲那些不能恢復(fù)所產(chǎn)生問題的轉(zhuǎn)化后的結(jié)構(gòu)異常。 3,、MFC中的異常 在MFC中,,所有的異常對象都是從CException基類(它有使用起來非常方便的GetErrorMessage和ReportError成員函數(shù))中派生來的,。大多數(shù)的MFC異常對象都是動(dòng)態(tài)分配的,,而且當(dāng)它們被捕獲時(shí),必須被刪除,,而沒有被捕獲的MFC異常由MFC本身在AfxCallWndProc函數(shù)中捕獲并刪除,。 4、異常的開銷 當(dāng)拋出C++異常時(shí),,函數(shù)調(diào)用鏈將從此回溯搜索,,尋找可以處理拋出這類異常的處理器。若沒找到,,進(jìn)程結(jié)束,。如果找到,調(diào)用棧將被釋放,,所有的自動(dòng)(局部)變量也將釋放,,然后棧將被整理為異常處理器的上下文相關(guān)設(shè)備。因此異常開銷由一個(gè)異常處理器目錄和一個(gè)活動(dòng)的自動(dòng)變量表(它需要額外的代碼,、內(nèi)存,,而且不論異常是否拋出,都會運(yùn)行),,還得加上函數(shù)調(diào)用鏈的搜索,、自動(dòng)變量的解析和棧的調(diào)整(它只在拋出異常的時(shí)候需要執(zhí)行)組成,。 5、異常策略 (1)拋出時(shí)機(jī) 拋出異常的時(shí)機(jī)應(yīng)該是一個(gè)函數(shù)發(fā)現(xiàn)一個(gè)錯(cuò)誤,,如果沒有一些特殊的操作,,該錯(cuò)誤能阻止程序正常的運(yùn)行,而這種操作它自己不能完成,,或是在函數(shù)不可能有返回值的時(shí)候,。 使用異常處理更簡單,更可靠,,更有效,,可以創(chuàng)建更健壯的代碼。然而,,應(yīng)該只在意外的情況下使用異常處理,。如果你認(rèn)為一個(gè)指針應(yīng)該是空值,這種條件下就直接在代碼中檢查這個(gè)值,,而不要使用異常,。 (2)何時(shí)捕獲 對于這個(gè)問題,有一些可能的標(biāo)準(zhǔn): .當(dāng)函數(shù)知道如何處理這個(gè)異常時(shí),。 .當(dāng)這個(gè)函數(shù)可以合理地處理這個(gè)異常而高級的函數(shù)不知道如何處理時(shí),。 .當(dāng)拋出異常可能使進(jìn)程崩潰時(shí),。 .當(dāng)函數(shù)可以繼續(xù)執(zhí)行它的任務(wù)時(shí),。 .當(dāng)需要整理分配好的資源時(shí)。 異常處理的一個(gè)缺點(diǎn)是它可能導(dǎo)致資源的泄露,。因此,,防止資源泄露更應(yīng)該是保持程序異常安全的一部分。棧釋放時(shí)會自動(dòng)整理局部變量,,但不包括動(dòng)態(tài)分配的變量,。可以使用智能(smart)指針來保護(hù)你的代碼在存在異常的情況下不會產(chǎn)生資源泄漏,。 (3)怎樣捕獲 .非MFC的C++異常應(yīng)該通過引用來捕獲,。使用引用捕獲異常不需要?jiǎng)h除異常對象(因?yàn)槭褂靡貌东@的異常會在棧中傳送),而且它保留了多態(tài)性(因此你捕獲的異常對象正是你拋出的異常對象),。 .MFC異常應(yīng)該通過指針來捕獲,。使用指針捕獲異常需要你刪除對象。因?yàn)樗鼈兺ǔ亩阎蟹峙?,?dāng)你處理完異常之后,,需要調(diào)用Delete成員函數(shù)來刪除。你不可以使用省略捕獲處理器捕獲MFC異常,,這會導(dǎo)致一個(gè)內(nèi)存泄露,。必須使用Delete成員函數(shù)刪除MFC異常,,而不用delete,因?yàn)橐恍㎝FC異常為靜態(tài)對象創(chuàng)建,。 在釋放棧的過程中拋出異常會導(dǎo)致進(jìn)程的終止,。釋放棧涉及到調(diào)用析構(gòu)函數(shù),異??梢宰柚拐{(diào)用delete操作符,,這樣會有資源泄漏,因此異常最好不要從析構(gòu)函數(shù)中拋出,。如果非要在析構(gòu)函數(shù)里拋出異常,,必須妥善處理,避免資源泄漏,。 6,、異常與防御性編程 在異常發(fā)生時(shí)繼續(xù)執(zhí)行程序,遠(yuǎn)比執(zhí)行一個(gè)正常的關(guān)閉動(dòng)作要重要,。如果可能,,應(yīng)該將精力集中在繼續(xù)執(zhí)行程序,并在必須的情況下才正常地關(guān)閉程序,??赡茏罡镜恼jP(guān)閉是一個(gè)在崩潰時(shí)可以重新啟動(dòng)自己的進(jìn)程,這是Windows資源管理器使用的一種技術(shù),。 如果一個(gè)與錯(cuò)誤相關(guān)的C++異常是可預(yù)料的,,如果它發(fā)生在非關(guān)鍵性的代碼中,如果它不是發(fā)生在程序啟動(dòng)或結(jié)束過程中或一個(gè)不可恢復(fù)的結(jié)構(gòu)異常的結(jié)果中,,這個(gè)程序就可以從其中恢復(fù),。 一旦你的程序可以從與錯(cuò)誤相關(guān)的異常中恢復(fù),,應(yīng)該先檢查程序的狀態(tài)和它的文檔,。如果程序和文檔已經(jīng)被破壞了,進(jìn)程也應(yīng)該終止運(yùn)行,。否則,,程序需要通知客戶機(jī)確定動(dòng)作的過程。如果客戶機(jī)同意執(zhí)行下去,,程序應(yīng)該恢復(fù)錯(cuò)誤并繼續(xù)執(zhí)行,。 四、返回值 并不是在所以場合下都能使用異常,,如在使用Windows API編程或帶有COM編程時(shí)并不使用異常,。在異常不適合的時(shí)候,使用返回值是一個(gè)好的辦法,。 返回值的基本特性: .返回值可以指示正常和不正常的函數(shù)運(yùn)行,,但不能阻止線程的繼續(xù)運(yùn)行,。 .返回值很容易被忽略。 .返回值在典型情況下是一個(gè)整數(shù),,通常映射符合于一個(gè)預(yù)定義的值,。 .返回值能高效地傳遞和接收。 因此,,返回值最適合用于以下的情形: .用于非錯(cuò)誤的狀態(tài)信息 .用于大多數(shù)情況下可以隨意忽略而不會出問題的錯(cuò)誤,。 .用于更易于出現(xiàn)在循環(huán)中的錯(cuò)誤。 .用于中間語言模塊如COM組件中的錯(cuò)誤,。
使用Visual C++調(diào)試器調(diào)試 一,、調(diào)試版本與發(fā)布版本 有時(shí)程序能在調(diào)試版本運(yùn)行但不能運(yùn)行于發(fā)布版本,反之也有可能,。一般說來,,一個(gè)發(fā)布版本意味著某些類型的優(yōu)化,而一個(gè)調(diào)試版本則沒有優(yōu)化,。下面我們來看看它們的區(qū)別: 1,、特別針對調(diào)試版本的編譯選項(xiàng) (1)/MDd,/MLd或者/MTd 調(diào)試版本的運(yùn)行時(shí)刻庫有調(diào)試符號,使用了調(diào)試堆,,調(diào)試堆的目的是發(fā)現(xiàn)內(nèi)存破壞和內(nèi)存泄漏,,并且向用戶報(bào)告源代碼的哪個(gè)地方出了問題。特性: .調(diào)試版本的運(yùn)行時(shí)刻庫對內(nèi)存的分配作了跟蹤,,允許用戶檢查內(nèi)存泄漏,。 .在剛分配的內(nèi)存里寫上0xCD的字節(jié)模式,用0xCD來填充剛分配的內(nèi)存,,有助于發(fā)現(xiàn)數(shù)據(jù)未被初始化的錯(cuò)誤,。 .在被釋放的內(nèi)存寫上0xDD的字節(jié)模式,有助于發(fā)現(xiàn)已被釋放的內(nèi)存,。 .在緩沖區(qū)的兩邊分配了四字節(jié)的保護(hù)數(shù)據(jù),,并用0xFD的字節(jié)模式作初始化,來檢查寫內(nèi)存的上溢出和下溢出,。 .在每個(gè)內(nèi)存分配的地方對源代碼文件名和行號作了記錄,,有助于用戶在源代碼中對內(nèi)存分配進(jìn)行定位。 (2)/Od 這個(gè)選項(xiàng)用來關(guān)閉優(yōu)化開關(guān),。因?yàn)槲幢粌?yōu)化的代碼直接對應(yīng)于源代碼,,所以比優(yōu)化后的代碼更容易讀懂。未被優(yōu)化的代碼編譯和鏈接會更快,,會有更短的調(diào)試周期,。而由于優(yōu)化,發(fā)布版本不見得會比調(diào)試版本運(yùn)行得好,,優(yōu)化代碼要求編譯器做一些假設(shè),,去除冗余,,但有時(shí)這個(gè)假設(shè)是錯(cuò)誤的,并且去掉的冗余也有可能隱藏錯(cuò)誤,。如發(fā)布版本的幀指針(EBP寄存器)省略(FPO)隱藏了函數(shù)原型不匹配的錯(cuò)誤,;在同步異常模式(只能由throw語句拋出,編譯器默認(rèn),,由/GX編譯選項(xiàng)設(shè)置)下,,異常處理程序可能被優(yōu)化掉,會阻止程序中的C++異常處理代碼安全地捕獲結(jié)構(gòu)異常,,在這種情況下,,你必須使用異步異常模式(采取任何指令都會產(chǎn)生異常的機(jī)制,由/Eha編譯選項(xiàng)設(shè)置),。 (3)/D “_DEBUG” 打開條件編譯調(diào)試代碼開關(guān),。只有這個(gè)符號被定義,調(diào)試代碼才會被編譯,,MFC使用_DEBUG符號來確定到底鏈接的是哪個(gè)版本的MFC類庫,。在調(diào)試版本中,內(nèi)聯(lián)默認(rèn)情況下是被關(guān)閉的,。 (4)/ZI 創(chuàng)建編輯繼續(xù)(Edit and Continue)的程序數(shù)據(jù)庫,。這個(gè)選項(xiàng)會打開/GF編譯選項(xiàng),/GF編譯選項(xiàng)會消除重復(fù)字符串,,并將字符串放到只讀內(nèi)存,。編輯繼續(xù)功能需要獲取存儲在PDB文件里的特殊信息來使得代碼的修改對調(diào)試器有效。如果被修改文件對應(yīng)的信息不在PDB文件里,,編輯繼續(xù)功能就不能進(jìn)行,,而且在調(diào)試過程中對代碼的任何修改都會出現(xiàn)下面的提示信息“On (5)/GZ 在調(diào)試版本中用來發(fā)現(xiàn)那些在發(fā)布版本里才發(fā)現(xiàn)的錯(cuò)誤,。其作用如下: .用0xCC模式初始化自動(dòng)(本地)變量,。 .在通過函數(shù)指針調(diào)用函數(shù)時(shí),檢查棧指針,,確認(rèn)是否有調(diào)用規(guī)則不匹配,。 .在函數(shù)最后檢查棧指針是否被改變,。 (6)/Gm 打開最小化重新鏈接開關(guān),,減少鏈接時(shí)間。 2,、特別針對發(fā)布版本的編譯選項(xiàng) (1)/MD,/ML或者/MT 使用發(fā)布版本的運(yùn)行時(shí)刻庫,。 (2)/O1或者/O2 打開優(yōu)化開關(guān),使得程序會最小或說速度會最快,,優(yōu)化器還可能發(fā)現(xiàn)代碼中潛在的錯(cuò)誤,,而這些錯(cuò)誤可能會被調(diào)試版本掩蓋,。 (3)/D “NDEBUG” 關(guān)閉條件編譯調(diào)試代碼開關(guān)。 (4)/GF 消除重復(fù)字符串并將它們放到只讀內(nèi)存中以避免被錯(cuò)誤地修改,。 (5)/Zi 創(chuàng)建包含調(diào)試符號的程序數(shù)據(jù)庫,。 如果一個(gè)錯(cuò)誤只發(fā)生在發(fā)布版本里,除非你是個(gè)匯編高手,,否則你需要調(diào)試符號來提示你到底程序出現(xiàn)了什么問題,,調(diào)試符號保存在程序的數(shù)據(jù)庫文件(PDB)中。Visual C++的AppWizard默認(rèn)情況下沒有為發(fā)布版本創(chuàng)建調(diào)試符號,。為創(chuàng)建調(diào)試符號,,打開工程設(shè)置對話框,選擇Win32 Release,,在C/C++標(biāo)簽里選擇Common類,,在調(diào)試信息里,如果是發(fā)布版本選擇Program Database,,如果是調(diào)試版本選擇Program Database for Edit and Continue(編輯繼續(xù)選項(xiàng)與優(yōu)化鏈接不相容,,不適于發(fā)布版本)。在Link標(biāo)簽里選擇Debug類,,然后選擇Debug Info和Microsoft format選項(xiàng),,最好不要選擇Separate types選項(xiàng),這樣所有的調(diào)試信息才會被合并到單獨(dú)的一個(gè)PDB文件中,。對于發(fā)布版本,,選擇Link標(biāo)簽,在Project options對話框的最后加上“/OPT:REF”,,這個(gè)選項(xiàng)使得不被引用的函數(shù)和數(shù)據(jù)不會出現(xiàn)在可執(zhí)行文件中,,避免了文件的無謂增大。對于調(diào)試版本不要使用這個(gè)選項(xiàng),,它會關(guān)閉增量鏈接(incremental linking),。 二、Visual C++編輯器的“設(shè)置”菜單 當(dāng)你打開或新建一個(gè)包含至少一個(gè)工程的Workspace后,,Visual C++的Project菜單中的“Settings…”命令就變?yōu)橛行?,選擇它或者按下熱鍵Alt+F7后,便可調(diào)出工程設(shè)置對話框,,這里面的選項(xiàng)將影響整個(gè)工程的建立和調(diào)試過程,,因此很重要。 在這個(gè)對話框中,,左上方的下拉列表框用于選擇一種工程配置,,包括有Win32 Debug、Win32 Release和All Configurations(指前兩種配置一起),某些選項(xiàng)在不同的工程配置中有不同的缺省值,。左邊的樹形視圖給出了當(dāng)前工程所有的文件及分類情況,。下面我們就以Win32 Debug為例來看看與工程有關(guān)的的四個(gè)主要選項(xiàng)卡的各自功能與含義(一共有十個(gè)選項(xiàng)卡): 1、 General選項(xiàng)卡 這個(gè)選項(xiàng)卡比較簡單,,從上向下的第一個(gè)選項(xiàng)用于更改使用MFC類庫的方式: DLL的方式或是靜態(tài)連接,。我們可以在兩種方式之間進(jìn)行切換。第二個(gè)選項(xiàng)用于指定在編譯連接過程中生成的中間文件和輸出文件的存放目錄,,對于調(diào)試版本來說,,缺省的目錄是工程下面的“Debug”子目錄。第三個(gè)選項(xiàng)用于指定是否允許每種工程配置都有自己的文件依賴關(guān)系(主要指頭文件),,由于絕大多數(shù)工程的調(diào)試版本和發(fā)布版本都具有相同的文件依賴關(guān)系,,所以通常不需要更改該選項(xiàng)。 2,、 Debug選項(xiàng)卡 Debug選項(xiàng)卡中是一些與調(diào)試有關(guān)的選項(xiàng),,由于選項(xiàng)比較多,它們被分成了幾個(gè)類,,我們可以從Category中選擇不同的類別,,選項(xiàng)卡就會切換顯示出相應(yīng)的選項(xiàng)。 在General類別中,,可以指定要調(diào)試的可執(zhí)行文件名,。另外三個(gè)選項(xiàng)可以指定用于調(diào)試的工作目錄,開始調(diào)試時(shí)給程序傳送的命令行參數(shù),,以及進(jìn)行遠(yuǎn)程調(diào)試時(shí)可執(zhí)行文件的路徑,。 3、C/C++選項(xiàng)卡 C/C++選項(xiàng)卡控制著Visual C++的編譯器,,其中的選項(xiàng)比較多,。下面有一個(gè)Project Options編輯框,里面列出的各種命令開關(guān)將會在開始編譯時(shí)作為命令行參數(shù)傳送給Visual C++的編譯器,。這些命令開關(guān)會跟隨其它選項(xiàng)改變而改變,。 在General類別中,Warning level用于指定編譯器顯示警告的級別,,如果選中了Warnings as errors,,那么顯示的每一個(gè)警告都將會引起一個(gè)錯(cuò)誤,這樣在編譯完畢后就無法啟動(dòng)連接器來進(jìn)行連接,。Optimizations用于設(shè)置代碼優(yōu)化方式,,優(yōu)化的目的主要有提高運(yùn)行速度和減小程序體積兩種,但有時(shí)候這兩種目的是相互矛盾的,。另外,,在極少數(shù)情況下,,不進(jìn)行優(yōu)化,,程序能正常運(yùn)行,,打開了優(yōu)化措施之后,程序卻會出現(xiàn)一些莫名其妙的問題,。其實(shí)這多半是程序中有潛在的錯(cuò)誤,,關(guān)閉優(yōu)化措施往往只是暫時(shí)解決問題。Debug info用于指定編譯器產(chǎn)生的調(diào)試信息的類型,,為了使用Visual C++的即編即調(diào)功能,,必須在這里選擇生成“Program Database for Edit and Continue”類型的調(diào)試信息。Preprocessor definitions是一些預(yù)先定義的宏名,。 C++ Language類別中的選項(xiàng)涉及到了C++語言的一些高級特性,,包括有成員指針的表示方式、異常處理,、運(yùn)行時(shí)類型信息,,一般情況下都不用改變它們。Co 在Listing Files類別中,,我們可以指定編譯器生成瀏覽信息和列表文件(Listing file),,前者可由瀏覽信息維護(hù)工具BSCMAKE生成瀏覽信息文件,后者則包含了C/C++源文件經(jīng)過編譯后對應(yīng)的匯編指令,。Optimizations類別允許我們對優(yōu)化措施進(jìn)行更細(xì)微的控制,,選擇了Customize后,便可以選擇進(jìn)行哪幾項(xiàng)優(yōu)化,,在Inline function expansion中我們可以指定對內(nèi)聯(lián)函數(shù)的擴(kuò)展方式,。Precompiled Headers類別中是關(guān)于預(yù)編譯頭文件的一些選項(xiàng),一般情況下都不用更改,。Preprocessor類別中是關(guān)于預(yù)處理的一些選擇,。 4、Link 選項(xiàng)卡 Link選項(xiàng)卡控制著Visual C++的連接器,。在General類別中,,可以指定輸出的文件名,,以及一些在連接過程中需要使用的額外的庫文件或目標(biāo)文件,下邊五個(gè)選項(xiàng)的含義分別為:生成調(diào)試信息,;忽略所有缺省的庫文件,;允許遞增連接方式(這種方式可以加快連接的速度);生成MAP文件,;允許進(jìn)行性能分析,。在Customize中選中Use program database允許使用程序數(shù)據(jù)庫。在Debug類別中,,我們可以指定調(diào)試信息的類別是Microsoft的格式,,還是COFF格式,或者兩種都有,,選中Separate types后連接器會把調(diào)試信息分開放在PDB文件中,,這樣連接起來會更快一些,但調(diào)試時(shí)速度卻會慢一些,。Input類別中是一些與輸入庫文件有關(guān)的選項(xiàng),,我們可以在這里指定使用或不使用某些庫文件或目標(biāo)文件。Output類別中則是一些與最終輸出的可執(zhí)行文件有關(guān)的選項(xiàng),,一般情況下都不用改變,。 三、Visual C++調(diào)試工具 1,、調(diào)試窗口 (1)觀察窗口(Watch) 調(diào)試程序時(shí),,可使用觀察窗口監(jiān)視變量和表達(dá)式。 (2)快速查看窗口(Quick watch) 功能和觀察窗口差不多,。 (3)變量窗口(Variables) 變量窗口有三個(gè)標(biāo)簽:Auto標(biāo)簽顯示了當(dāng)前語句和前一條語句用到的變量,,Locals標(biāo)簽顯示當(dāng)前函數(shù)的局部變量,this標(biāo)簽顯示了this指針執(zhí)行的對象,。 (4)寄存器窗口(Register) 可以監(jiān)視CPU的寄存器,、標(biāo)志值以及浮點(diǎn)堆棧 (5)內(nèi)存窗口(Memory) 可顯示從一特定地址開始的虛擬內(nèi)存。Address框允許你指定從哪個(gè)虛擬內(nèi)存地址開始顯示,。 (6)調(diào)用棧窗口(Call stack) 可顯示引起當(dāng)前源代碼語句執(zhí)行的一系列函數(shù)調(diào)用,,當(dāng)前函數(shù)在堆棧的頂端。 (7)反匯編窗口(Disassembly) 可查看編譯器生成的對應(yīng)于源代碼的匯編指令,。 2,、調(diào)試符號 程序數(shù)據(jù)庫文件(.pdb)包含了Visual C++調(diào)試器所需的調(diào)試信息和程序信息。調(diào)試信息包含了變量的名字和類型,、函數(shù)原型,、源代碼行號、類和結(jié)構(gòu)的布局,、FPO調(diào)試信息(重建堆棧幀)以及進(jìn)行增量鏈接所需的信息,。對于設(shè)置了Program Database for Edit and Continue選項(xiàng)的程序,,PDB還要包含執(zhí)行編輯繼續(xù)功能所需的信息。 3,、使用斷點(diǎn) 斷點(diǎn)(BreakPoint)是運(yùn)行你向調(diào)試器描述環(huán)境,,并讓調(diào)試器設(shè)置好程序狀態(tài)的一種機(jī)制。如果沒有斷點(diǎn),,只有在程序里一步一步跟蹤使用調(diào)試器,。在Visual C++中,,你可以設(shè)置三種類型的斷點(diǎn):代碼定位斷點(diǎn),、數(shù)據(jù)斷點(diǎn)和消息斷點(diǎn)。 四,、提高調(diào)試器的查錯(cuò)能力 盡量采用編譯時(shí)刻檢查而不是運(yùn)行時(shí)刻檢查,。 1、使用最高的編譯警告級別/W4 象if(x=2)這樣的語句,,默認(rèn)的警告級別為/W3時(shí)不顯示任何信息,,但改成最高警告級別/W4時(shí)則會出現(xiàn)“waning C4706:assignment within conditional expr 2,、在調(diào)試版本中使用/GZ編譯選項(xiàng) /GZ選項(xiàng)用來發(fā)現(xiàn)那些在發(fā)布版本里才發(fā)現(xiàn)的錯(cuò)誤,包括未被初始化的自動(dòng)(局部)變量,、堆棧錯(cuò)誤,、不正確的函數(shù)原型等。 3,、使用#pragma warning編譯器指示 你可以使用#pragma warning編譯器指示來禁止整個(gè)程序,、特定的頭文件、特定的代碼文件或是特定的某一行代碼的特定警告,,這看你把#pragma放在哪里,。 4、使用沒有警告的編譯法則/WX 這個(gè)編譯選項(xiàng)把所有的警告當(dāng)成錯(cuò)誤來對待,,只有在假警告被消除之后才能應(yīng)用,。有時(shí)編譯警告可能是合理的,處理編譯警告的核心是要發(fā)現(xiàn)錯(cuò)誤,,而不是抑制警告本身,。這個(gè)法則對于大的程序開發(fā)小組來說很有幫助。最終目標(biāo)是消除錯(cuò)誤,,而不是消除警告,。 五、內(nèi)存空間與分配 1,、內(nèi)存分配錯(cuò)誤 動(dòng)態(tài)內(nèi)存分配錯(cuò)誤有兩種基本類型:內(nèi)存錯(cuò)誤和內(nèi)存泄漏,。 (1)內(nèi)存錯(cuò)誤 當(dāng)一個(gè)指針或者該指針?biāo)赶虻膬?nèi)存單元成為無效單元,,或者內(nèi)存中分配的數(shù)據(jù)結(jié)構(gòu)被破壞時(shí),就會造成內(nèi)存錯(cuò)誤,。指針未被初始化,,指針被初始化為一個(gè)無效地址,指針被不小心錯(cuò)誤地修改,,在與指針相關(guān)聯(lián)的內(nèi)存區(qū)域被釋放后使用該指針(這種指針被稱為虛懸(dangling)指針),,這些都會使指針變?yōu)闊o效指針。當(dāng)通過一個(gè)錯(cuò)誤指針或者虛懸指針對內(nèi)存進(jìn)行寫入,,或者將指針強(qiáng)制轉(zhuǎn)換為不匹配的數(shù)據(jù)結(jié)構(gòu),,又或者是寫數(shù)據(jù)越界,內(nèi)存自身也會遭到破壞,。刪除未被初始化的指針,、刪除非堆指針、多次刪除同一指針或者覆蓋一個(gè)指針的內(nèi)部數(shù)據(jù)結(jié)構(gòu),,都會造成內(nèi)存分配系統(tǒng)錯(cuò)誤,。 (2)內(nèi)存泄漏 內(nèi)存泄漏在被動(dòng)態(tài)分配的內(nèi)存沒有被釋放時(shí)產(chǎn)生。有許多情況會導(dǎo)致內(nèi)存泄漏,,如沒有在程序的全部執(zhí)行路徑中釋放內(nèi)存,,沒有在析構(gòu)函數(shù)中釋放所有的內(nèi)存等。一個(gè)程序在崩潰之前可運(yùn)行的時(shí)間越長,,則導(dǎo)致崩潰的原因與內(nèi)存泄漏的關(guān)系越大,。 Windows會在程序結(jié)束的時(shí)候?qū)⑿孤┑膬?nèi)存收回,因此內(nèi)存泄漏是個(gè)暫時(shí)性的問題,。但為什么必須消除內(nèi)存泄露呢,?首先,內(nèi)存泄漏往往會導(dǎo)致系統(tǒng)資源的泄漏,。動(dòng)態(tài)分配內(nèi)存往往不僅僅代表一塊存儲區(qū)域,,還代表了某些類型的系統(tǒng)資源,如文件,、窗口,、設(shè)備上下文、GDI對象等,。其次,,高質(zhì)量的程序和特定的服務(wù)器程序必須能夠無限地運(yùn)行下去。最后,,內(nèi)存泄漏往往是其他程序錯(cuò)誤或不良編程習(xí)慣的征兆,。 導(dǎo)致內(nèi)參泄漏的原因:忘記釋放內(nèi)存;構(gòu)造函數(shù)失??;存在內(nèi)存泄漏的析構(gòu)函數(shù),;存在內(nèi)存泄漏的異常處理程序;多個(gè)返回語句,;使用錯(cuò)誤形式的delete,。 2、關(guān)于內(nèi)存的初始化 在調(diào)試版本里,,堆里未被初始化的內(nèi)存被0xCD字節(jié)模式填充,,堆里釋放的內(nèi)存被0xDD字節(jié)模式填充。堆棧里被初始化的內(nèi)存被0xCC字節(jié)模式填充,。調(diào)試版本和發(fā)布版本里,,未被初始化的全局內(nèi)存都被初始化為0。 3,、內(nèi)存虛擬地址空間 Windows使用一組固定的范圍來分割進(jìn)程的4GB虛擬地址空間,,因此有時(shí)可通過查看指針的返回值來判斷指針是否有效,。 (1)Windows2000虛擬地址空間劃分 0~0XFFFF(64KB):不能用來檢測空指針賦值(訪問沖突) 0x10000(64KB)~0x7FFEFFFF(2GB-64KB):Win32進(jìn)程私有的(非保留的),,用于程序代碼和數(shù)據(jù) 0x7FFF0000(2GB-64KB)~0x7FFFFFFF(2GB):不能用來防止覆蓋OS分區(qū)(訪問沖突) 0x800000000(2GB)~0xFFFFFFFF(4GB):為操作系統(tǒng)保留,不可訪問(訪問沖突) (2)Windows2000虛擬地址空間使用 0x00030000~0x0012FFFF:線程棧 0x00130000~0x003FFFFF:堆(有時(shí)堆位于此處) 0x00400000~0x005FFFFF:可執(zhí)行代碼 0x00600000~0x0FFFFFFF:堆(有時(shí)堆位于此處) 0x10000000~0x5FFFFFFF:App DLLs,、Msvcrt.dll,、Mfc42.dll 0x77000000~0xFFFFFFFF:Advapi32.dll、Comctl32.dll,、Gdi32.dll,、Kernel32.dll、Ntdll.dll,、Rpcrt4.dll,、Shell32.dll、User32.dll 其中,,0x00400000是所有版本的Windows能使用的最低基地址,。 六、一些調(diào)試技術(shù) 1,、調(diào)試死循環(huán) 使用Debug菜單下的Break命令,。在Windows2000中,如果程序有輸入請求,,可以使用F12鍵中斷程序,,然后檢查窗口的調(diào)用棧,或單步跟蹤代碼找到死循環(huán)的發(fā)生原因,。 2,、用Spy++調(diào)試與消息有關(guān)的問題 調(diào)試消息的最好方案是使用Visual C++提供的Spy++工具。Spy++允許程序員查看窗口,、消息,、進(jìn)程和線程,。Spy++默認(rèn)的消息輸出:第一欄顯示行號。第二欄顯示接受消息的句柄,。第三欄中的“S”表示消息是用SendMessage發(fā)出的,,“P”代表消息是由PostMessage發(fā)出的,“R”是消息句柄的返回值,。第四欄給出解碼后的消息名,,消息參數(shù)或返回值。 3,、非常規(guī)方法 (1)重新編連你的應(yīng)用程序 當(dāng)你的程序表現(xiàn)出異常的或意外的行為,,或者Visual C++編譯器因?yàn)橐粋€(gè)內(nèi)部編譯器錯(cuò)誤而失敗時(shí),最好刪除工程中的Debug或Release文件夾,,從頭開始重新進(jìn)行編連,。 (2)重新啟動(dòng)Visual C++ Visual C++有超強(qiáng)的能力,但編譯器的某些特性也會引起奇怪的錯(cuò)誤,。如果你的程序表現(xiàn)得很奇怪,,你可是試著清除所有的斷點(diǎn),關(guān)閉或隱藏觀察窗口,,檢查工程設(shè)置對話框看最近做了什么修改,,直至重新啟動(dòng)Visual C++以便消除由于Visual C++環(huán)境引起的異常行為。 (3)重新啟動(dòng)Windows 當(dāng)你發(fā)現(xiàn)Windows或者其他程序表現(xiàn)出異常的或出人意料的行為時(shí),,就應(yīng)該重新啟動(dòng)Windows,,以消除操作系統(tǒng)給調(diào)試帶來的干擾。 |
|