編寫易于調(diào)試的VC代碼(轉(zhuǎn)載)
提供者:bluejoe 張貼時間:2007-01-23 17:00:05.0 出處:csdn.net 作者:michael
一 程序的設(shè)計
要避免錯誤,首先要從好的設(shè)計開始。對于程序的設(shè)計,需考慮到程序的兩個特性:
1簡單性
大多數(shù)常見的錯誤來源于程序設(shè)計中不必要的復(fù)雜成分。一個好的設(shè)計應(yīng)該反映問題本身的要求,而不必為了刻意追求“滿足將來的需要”而添加不必要的特性。實(shí)際上,簡單優(yōu)雅的設(shè)計比那些復(fù)雜的設(shè)計更能迎合未來的需求。
2 耦合性
耦合(decoupling)性用來衡量不同對象之間的依賴程度。松耦合的程序易于理解和實(shí)現(xiàn),易于測試和維護(hù),且這種程序包含錯誤的可能性小,錯誤也較容易發(fā)現(xiàn)和清除。
二 編程風(fēng)格
編程風(fēng)格是個人問題,有很大的隨意性。一個好的編程風(fēng)格不僅讓代碼易理解,也易于調(diào)試。好的編程風(fēng)格包括:
1 清晰地書寫代碼
如果沒有必要,盡量不要使用語言中的高級特性,因?yàn)檫@些特性不易于理解和調(diào)試。使用大多數(shù)程序員都能理解的語言成分來書寫代碼不易犯錯且易于理解和維護(hù)。
2 編寫結(jié)構(gòu)良好的代碼
當(dāng)程序崩潰時所能得到的最基本的調(diào)試信息是源代碼文件、問題所在行的行號和一個調(diào)用棧(call stack)。調(diào)用棧是調(diào)試程序時最有幫助的部分,它提供錯誤出現(xiàn)的上下文,也就是帶參數(shù)的函數(shù)調(diào)用序列。你書寫的代碼結(jié)構(gòu)越好,調(diào)用棧就能給你越多信息。
3 使用良好的標(biāo)識符
一個好名字能使你的代碼更容易被理解和維護(hù)。流行的匈牙利命名法(Hungarian Notation)實(shí)際上是把標(biāo)識符的意義和表示方法結(jié)合起來。 現(xiàn)在,匈牙利命名法表現(xiàn)出不少的局限性,匈牙利命名法過于看重前綴的作用,對一個變量的表達(dá)信息不完整,實(shí)際上并沒有傳遞多少有用信息,它使代碼難于閱 讀,難以維護(hù)。一個好的命名傳統(tǒng)是指示出變量的作用域以便在需要的時候檢查它的定義,并明確地指出一個變量是全局的、局部的還是成員數(shù)據(jù)。依賴變量的定義 比依賴匈牙利前綴更加有用和可靠。
好的名字能夠用平常的語言概括出該標(biāo)識符所代表的實(shí)體的含義。在選擇類、函數(shù)、變量的名字時可以考慮以下幾個原則:
取簡單的描述性名字,好的名字能簡要地概括出這個標(biāo)識符代表的含義。
避免簡寫,簡寫使標(biāo)識符難于閱讀和記憶,盡量使用混合大小寫的完整的單詞。
避免相似性的文字,避免混淆。
避免采用一般的或隨機(jī)產(chǎn)生的名字,而應(yīng)采用有實(shí)際意義的名字。如欲從按鈕類派生位圖按鈕,取一個CBitmapButton,而不是CMyButton。
4 用簡單的語句行
在VC中,一行可寫多個語句。但調(diào)試是面向行的,過于復(fù)雜的行難于調(diào)試。因此,從調(diào)試的角度出發(fā),每一個語句都應(yīng)獨(dú)自成行。
5 使用統(tǒng)一的排列
統(tǒng)一的排列方式使類、變量的定義和語句更加明顯。
6 用括號使書寫清晰
你不一定能都記住各種運(yùn)算符的優(yōu)先級和結(jié)合律,而使用多余的括號并不影響編譯后的代碼。因此,如果你不能確定是否需要括號時,請加上它。
7 使用好的注釋
用好的注釋能使你的代碼不易出錯,而且便于其他程序員閱讀,便于理解和維護(hù)。
三 編寫程序時應(yīng)注意的問題
1 充分利用VC++的特性
可用下列技術(shù)來充分利用VC++的編譯器的特性:
(1)用const代替#define來創(chuàng)建常量;
(2)用enum代替#define來創(chuàng)建常量集合;
(3)用內(nèi)聯(lián)(inline)函數(shù)代替#define;
這三種技術(shù)用C++而不是C預(yù)處理。使用預(yù)處理的問題在于編譯器對于預(yù)處理器所作的事情一無所知,因此無法用數(shù)據(jù)類型檢查錯誤和不一致的地方。預(yù)處理的 名字不在符號表里,因此也不能用調(diào)試工具來檢查預(yù)處理常量。相似地,預(yù)處理宏被編譯進(jìn)去,不能用調(diào)試工具跟蹤。編譯器能充分了解const、enum和 inline語句,從而能在編譯的時候?qū)Τ霈F(xiàn)的問題發(fā)出警告。
但預(yù)處理在很多調(diào)試代碼中起重要作用。調(diào)試代碼經(jīng)常需要從非調(diào)試代碼里面得到不同的行為,而最有效的辦法就是讓預(yù)處理為調(diào)試創(chuàng)建不同的代碼。
(4)用new和delete代替malloc和free;
在創(chuàng)建對象、類型的安全性和靈活性方面。使用new/delete比malloc/free要好。另外,new可被重載,提供了更大的靈活性。
(5)用輸入輸出流(iostreams)代替stdio。
使用C++輸入輸出流(<<和>>)而不使用C標(biāo)準(zhǔn)輸入輸出庫(printf/sprintf和scanf/sscanf), 有利于安全性和擴(kuò)展性。從調(diào)試的角度來看,標(biāo)準(zhǔn)輸入輸出函數(shù)的最大問題在于編譯器不能對控制流參數(shù)進(jìn)行任何類型檢測,而輸入輸出流的任何問題都能在編譯時 檢測出來。
2 使用頭文件
要在頭文件中聲明所有共享的外部符號,而且保留函數(shù)原型中的參數(shù)名。把所有的共享定義放在頭文件中,不要在.cpp文件里面看到extern關(guān)鍵字。
3 初始化變量
在使用變量之前一定要把它們初始化。在初始化之前就使用變量肯定會產(chǎn)生錯誤。通常不需對對象進(jìn)行初始化,對對數(shù)據(jù)成員應(yīng)在構(gòu)造函數(shù)中初始化。必須明確地 為在棧中和堆中分配的數(shù)組和數(shù)據(jù)結(jié)構(gòu)進(jìn)行初始化。對于對象,應(yīng)該初始化每個需要初始化的數(shù)據(jù)成員。因?yàn)樽兞康氖褂檬怯蓛?yōu)化器來檢查的,所以檢測未初始化的 本地變量,發(fā)布版本要比調(diào)試版本要做得好。
4 使用布爾表達(dá)式
C++的布爾類型:bool,值為true和false,大小為一個字節(jié)。
Windows程序通常用BOOL類型。定義如下:
1

Typedef int BOOL;
2
3

#define FALSE 0
4
5

#define TRUE 1
在C++中,一個布爾表達(dá)式如果為0則為假,其他則為真。因此,對布爾表達(dá)式應(yīng)該檢查是否問假而不是檢查是否為真。
5 使用句柄和指針
初始化一個指針時,要么讓其指向一個有效的內(nèi)存地址,要么設(shè)為0(空指針),避免指針指向無效地址。回收指針?biāo)笇ο髸r要重新初始化這個指針,并且在指針被釋放前為空時就對其進(jìn)行處理。對句柄的處理跟指針一樣。
6 用引用而不是指針做參數(shù)
用指針做函數(shù)的參數(shù)可傳遞一個空指針,很靈活,但也很容易忘了對指針進(jìn)行初始化。而引用是對象的別名,它必須和有效的對象相關(guān)聯(lián),不存在空的和沒有初始化的引用。當(dāng)在函數(shù)中收到一個引用參數(shù)時,可以肯定這是一個有效的對象。程序用引用做參數(shù)比用指針做參數(shù)更為健壯。
7 強(qiáng)制類型轉(zhuǎn)換(cast)
進(jìn)行數(shù)據(jù)類型的強(qiáng)制類型轉(zhuǎn)換時,將會調(diào)用相應(yīng)的構(gòu)造函數(shù)或轉(zhuǎn)換函數(shù)來創(chuàng)建一個新類型的臨時對象。對指針的正確類型轉(zhuǎn)換可消除一個編譯錯誤,但并沒改變指 針。強(qiáng)制類型轉(zhuǎn)換破壞了編譯器進(jìn)行類型檢查的功能,而這正是編譯器查找錯誤的最有效的機(jī)制。為了保證安全性,每一個強(qiáng)制類型轉(zhuǎn)換都需要手工進(jìn)行類型檢查。 為盡量避免強(qiáng)制類型轉(zhuǎn)換,你可以:避免使用多態(tài)數(shù)據(jù)類型;使用更加廣泛的基類;提供特殊的存取函數(shù);讓編譯器隱式處理類型轉(zhuǎn)換等措施。
8 使用構(gòu)造函數(shù)和析構(gòu)函數(shù)
構(gòu)造函數(shù)需要分配內(nèi)存,創(chuàng)建資源或者打開文件,這些運(yùn)算并不總是成功。構(gòu)造函數(shù)沒有返回值,沒有直接顯示錯誤的方法。一個常見的方法(在很多MFC類中 使用)是把對象創(chuàng)建分為兩步:第一步,讓構(gòu)造函數(shù)以一種不會出錯的方式初始化對象;第二步,讓某些初始化函數(shù)(如Init或Open)完成工作,這一步可 能出錯。另一種方法是在構(gòu)造函數(shù)中使用異常:第一步,以不會出錯的方式初始化對象;第二步,用可能在try段內(nèi)出錯的代碼初始化對象;第三步,在 catch代碼里面處理異常。如果出現(xiàn)異常,就會在構(gòu)造函數(shù)里清除分配的資源,并且再次拋出異常。
異常處理的一個關(guān)鍵細(xì)節(jié)就是在棧展 開的過程中拋出的異常會終止整個應(yīng)用程序。在處理異常時經(jīng)常要調(diào)用析構(gòu)函數(shù),因此析構(gòu)函數(shù)很容易出錯,一定要保證析構(gòu)函數(shù)的異常在析構(gòu)函數(shù)中得到處理。要 保證基類的析構(gòu)函數(shù)是虛函數(shù)。這樣,就算對象是一個指向基類的指針,也會調(diào)用派生類的析構(gòu)函數(shù)。否則,就會引起資源泄漏(resource leak)。
在VC程序中使用調(diào)試語句
為了更好地對程序調(diào)試,可以使用如下方法:使用斷言、使用跟蹤語句、使用異常和返回值。
一、斷言
1、基本概念
斷言是一種讓錯誤在運(yùn)行時候自我暴露的簡單有效實(shí)用的技術(shù)。它們幫助你較早較輕易地發(fā)現(xiàn)錯誤,使得整個調(diào)試過程效率更高。
斷言是布爾調(diào)試語句,用來檢測在程序正常運(yùn)行的時候某一個條件的值是否總為真,它能讓錯誤在運(yùn)行時刻暴露在程序員面前。使用斷言的最大好處在于,能在更解決錯誤的發(fā)源地的地方發(fā)現(xiàn)錯誤。斷言具有以下特征:
.斷言是用來發(fā)現(xiàn)運(yùn)行時刻錯誤的,發(fā)現(xiàn)的錯誤是關(guān)于程序?qū)崿F(xiàn)方面的。
.斷言中的布爾表達(dá)式顯示的是某個對象或者狀態(tài)的有效性而不是正確性。
.斷言在條件編譯后只存在于調(diào)試版本中,而不是發(fā)布版本里。
.斷言不能包含程序代碼。
.斷言是為了給程序員而不是用戶提供信息。
使用斷言最根本的好處是自動發(fā)現(xiàn)許多運(yùn)行時產(chǎn)生的錯誤,但斷言不能發(fā)現(xiàn)所有錯誤。斷言檢查的是程序的有效性而不是正確性,可通過斷言把錯誤限制在一個有 限的范圍內(nèi)。當(dāng)斷言為假,激活調(diào)試器顯示出錯代碼時,可用Call Stack命令,通過檢查棧里的調(diào)用上下文、少量相關(guān)參數(shù)的值以及輸出窗口中 Debug表的內(nèi)容,通常能檢查出導(dǎo)致斷言失敗的原因。_ASSERTE宏(屬于C運(yùn)行時間庫)還能在斷言失敗時顯示出失效斷言。下面我們討論一下MFC 庫中的斷言。
2、MFC庫中的斷言
(1) ASSERT(布爾表達(dá)式)
用MFC時最好選擇ASSERT宏,它的優(yōu)點(diǎn)是即使出現(xiàn)了WM_QUIT消息也能顯示斷言失效消息框。
(2) VERIFY(布爾表達(dá)式)
VERIFY 宏中的布爾表達(dá)式在發(fā)布版本中被保留下來。VERIFY宏簡化了對函數(shù)返回值的檢查,一般用來檢查Windows API的返回值。由于 VERIFY宏里的布爾表達(dá)式在發(fā)布版本里保留了下來,因此最好盡量不要使用這個宏以實(shí)現(xiàn)程序代碼和調(diào)試代碼的完全分離。
(3 )ASSERT_VALID(指向CObject派生類對象的指針)
ASSERT_VALID宏通過調(diào)用重載的AssertValid函數(shù)來確定指向CObject派生類對象的指針是否有效。無論你什么時候從CObject派生類中得到一個對象,在對這個對象做任何操作之前都應(yīng)該調(diào)用ASSERT_VALID宏。
(4) ASSERT_KINDOF(類名, 指向CObject派生類對象的指針)
這個宏用來驗(yàn)證指向CObject派生類對象的指針是否從某個特殊類中派生,在調(diào)用它之前先調(diào)用ASSERT_VALID宏。只有在很特殊的場合下才用得到,如檢測編譯器可能錯過的對象類型問題。
此外,還有兩個沒有正式文件的ASSERT宏的變種:ASSERT_POINTER(指針,指針類型),ASSERT_NULL_OR_POINTER(指針,指針類型)。
3、什么時候使用斷言
把斷言看作一種簡單的制造柵欄的方法,這種柵欄能使錯誤在穿過自己時暴露。
.檢查函數(shù)的輸入
.檢查函數(shù)的輸出
.檢查對象的當(dāng)前狀態(tài)
.堅持邏輯變量的合理性和一致性
.檢查類中的不變量
公有成員函數(shù)比私有和保護(hù)的成員函數(shù)需要更全面的斷言。
不正確地使用斷言會導(dǎo)致錯誤。斷言應(yīng)該檢測那些在程序正常運(yùn)行的時候永遠(yuǎn)都不可能出現(xiàn)的狀態(tài)。斷言是用來揭示錯誤的,而不是用來糾正運(yùn)行時刻錯誤的。
4、斷言與防御性編程(Defensive Programming)
斷言在調(diào)試的時候向程序員揭示運(yùn)行時刻錯誤(調(diào)試版本里),而防御性編程使用戶在運(yùn)行程序(發(fā)布版本里)時,當(dāng)出現(xiàn)意外情況時程序仍能繼續(xù)工作。實(shí)際 上,防御性的編程要求程序在檢測到意外時返回一個“安全”的值(比如布爾函數(shù)返回false,指針和句柄返回空值),一個錯誤代碼或者拋出一個異常來解決 問題。特定的防御性編程技術(shù)包括:處理無效函數(shù)參數(shù)和數(shù)據(jù)、出現(xiàn)問題的時候程序失敗、檢查臨界函數(shù)返回的錯誤代碼以及處理異常。需要防御性編程的標(biāo)準(zhǔn)問題 包括:錯誤的輸入數(shù)據(jù)、內(nèi)存或者硬盤空間不夠、不能打開一個文件、外部設(shè)備不能訪問、網(wǎng)絡(luò)連接不上或者甚至在程序中還有錯誤,目的是保持程序的運(yùn)行狀態(tài)。 如果你的程序是防御性的,別忘了使用斷言。如果你使用斷言,也別忘了防御性編程。這兩種技術(shù)最好結(jié)合在一起使用。
二、跟蹤語句
1、基本概念
跟蹤語句(trace statements)可使程序執(zhí)行,并使程序員可對可變值進(jìn)行查看。它們提供了一個用于觀察的程序,并且獨(dú)立于一個交互式的調(diào) 試器,但是最具有特色的是它們常用于對調(diào)試器提供的信息進(jìn)行補(bǔ)充。在VC中,跟蹤消息通常輸出到輸出窗口中的Debug標(biā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宏有以下形式:
1

_TRACE(reportType,format);
2
3

_TRACE0(reportType,format,arg1);
4
5

_TRACE1(reportType,format,arg1,arg2);
6
7

_TRACE2(reportType,format,arg1,arg2,arg3);
8
9

_TRACE3(reportType,format,arg1,arg2,arg3,arg4);
在MFC中,推薦使用TRACEn宏,當(dāng)使用TRACE宏時需要使用_T宏來格式化參數(shù)以正確解決Unicode的校正,而TRACEn不需要。
MFC TRACE宏中的一個缺點(diǎn)是AfxTrace函數(shù)使用一個512字符固定大小的緩沖區(qū),這使得它在跟蹤長字符串時是無用的。
(2)CObject::Dump
CObject類有一個轉(zhuǎn)儲(dump)虛擬函數(shù),所有繼承CObject的類都可以通過重載這個函數(shù),輸出它們的值。
3、Visual C++消息Pragma
消息Pragma實(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)你的程序中有錯誤而你想得到更多信息的時候,你應(yīng)該去查看一下跟蹤消息。由于VC輸出窗口的緩沖區(qū)是有大小限制的,因此如果跟蹤消息數(shù)據(jù)產(chǎn)生的速度 超過輸出窗口處理的速度,那么消息會塞滿緩沖區(qū),導(dǎo)致數(shù)據(jù)丟失。避免這個問題的簡單方法是在輸出大量數(shù)據(jù)的代碼段如轉(zhuǎn)儲對象時,調(diào)用Sleep API函 數(shù)。
三、異常
1、基本概念
錯誤是一種條件,在這種條件下,如果不執(zhí)行額外的處理,線程就不能正常地 執(zhí)行下去。異常是用于處理錯誤的。使用異常的一個很明顯的好處就是它們通過發(fā)出錯誤信號,可以讓程序代碼和錯誤處理代碼分開,而且不會讓程序忽略錯誤,你 不用不斷地檢查函數(shù)的返回值,因此它們將程序代碼簡單化。另一個好處是它們不需要嚴(yán)格的編程作風(fēng)。
異常的基本特性:
.異常是基于每個進(jìn)程而提出并處理的。
.異常不能被線程忽略,必須被處理。
.未處理的異常會使進(jìn)程結(jié)束,而不僅僅是結(jié)束線程。
.異常出來在釋放棧時會釋放所有的棧對象,避免了資源的漏洞。
.異常處理需要大量的額外操作,使得它不適于經(jīng)常運(yùn)行的代碼。
.可以拋出任何類型的異常對象,除了整數(shù)。
如果正確執(zhí)行,異常處理有下面的特性:
.異常是不是正常的運(yùn)行結(jié)果,是特殊情況。
.異常在返回值無效的情況下使用。
.異常是可靠的,不可能被忽略。
.異常簡化了錯誤處理,簡化了程序代碼,使錯誤處理更加方便。
Visual C++的默認(rèn)情況下,在調(diào)試版本中處理異常,而在發(fā)布版本中并不進(jìn)行處理。由于異常也是錯誤,Windows異常碼采用了同 Windows錯誤碼一樣的位映射模式,為一個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ù)安裝一個結(jié)構(gòu)異常向C++異常的轉(zhuǎn)化器,但不要捕獲那些不能恢復(fù)所產(chǎn)生問題的轉(zhuǎn)化后的結(jié)構(gòu)異常。
3、MFC中的異常
在MFC中,所有的異常對象都是從CException基類(它有使用起來非常方便的GetErrorMessage和ReportError成員函 數(shù))中派生來的。大多數(shù)的MFC異常對象都是動態(tài)分配的,而且當(dāng)它們被捕獲時,必須被刪除,而沒有被捕獲的MFC異常由MFC本身在 AfxCallWndProc函數(shù)中捕獲并刪除。
4、異常的開銷
當(dāng)拋出C++異常時,函數(shù)調(diào)用鏈將從此回溯搜索,尋 找可以處理拋出這類異常的處理器。若沒找到,進(jìn)程結(jié)束。如果找到,調(diào)用棧將被釋放,所有的自動(局部)變量也將釋放,然后棧將被整理為異常處理器的上下文 相關(guān)設(shè)備。因此異常開銷由一個異常處理器目錄和一個活動的自動變量表(它需要額外的代碼、內(nèi)存,而且不論異常是否拋出,都會運(yùn)行),還得加上函數(shù)調(diào)用鏈的 搜索、自動變量的解析和棧的調(diào)整(它只在拋出異常的時候需要執(zhí)行)組成。
5、異常策略
(1)拋出時機(jī)
拋出異常的時機(jī)應(yīng)該是一個函數(shù)發(fā)現(xiàn)一個錯誤,如果沒有一些特殊的操作,該錯誤能阻止程序正常的運(yùn)行,而這種操作它自己不能完成,或是在函數(shù)不可能有返回值的時候。
使用異常處理更簡單,更可靠,更有效,可以創(chuàng)建更健壯的代碼。然而,應(yīng)該只在意外的情況下使用異常處理。如果你認(rèn)為一個指針應(yīng)該是空值,這種條件下就直接在代碼中檢查這個值,而不要使用異常。
(2)何時捕獲
對于這個問題,有一些可能的標(biāo)準(zhǔn):
.當(dāng)函數(shù)知道如何處理這個異常時。
.當(dāng)這個函數(shù)可以合理地處理這個異常而高級的函數(shù)不知道如何處理時。
.當(dāng)拋出異常可能使進(jìn)程崩潰時。
.當(dāng)函數(shù)可以繼續(xù)執(zhí)行它的任務(wù)時。
.當(dāng)需要整理分配好的資源時。
異常處理的一個缺點(diǎn)是它可能導(dǎo)致資源的泄露。因此,防止資源泄露更應(yīng)該是保持程序異常安全的一部分。棧釋放時會自動整理局部變量,但不包括動態(tài)分配的變量。可以使用智能(smart)指針來保護(hù)你的代碼在存在異常的情況下不會產(chǎn)生資源泄漏。
(3)怎樣捕獲
.非MFC的C++異常應(yīng)該通過引用來捕獲。使用引用捕獲異常不需要刪除異常對象(因?yàn)槭褂靡貌东@的異常會在棧中傳送),而且它保留了多態(tài)性(因此你捕獲的異常對象正是你拋出的異常對象)。
.MFC 異常應(yīng)該通過指針來捕獲。使用指針捕獲異常需要你刪除對象。因?yàn)樗鼈兺ǔ亩阎蟹峙洌?dāng)你處理完異常之后,需要調(diào)用Delete成員函數(shù)來刪除。你不可以 使用省略捕獲處理器捕獲MFC異常,這會導(dǎo)致一個內(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ā)生時繼續(xù)執(zhí)行程序,遠(yuǎn)比執(zhí)行一個正常的關(guān)閉動作要重要。如果可能,應(yīng)該將精力集中在繼續(xù)執(zhí)行程序,并在必須的情況下才正常地關(guān)閉程序。可能最根本的正常關(guān)閉是一個在崩潰時可以重新啟動自己的進(jìn)程,這是Windows資源管理器使用的一種技術(shù)。
如果一個與錯誤相關(guān)的C++異常是可預(yù)料的,如果它發(fā)生在非關(guān)鍵性的代碼中,如果它不是發(fā)生在程序啟動或結(jié)束過程中或一個不可恢復(fù)的結(jié)構(gòu)異常的結(jié)果中,這個程序就可以從其中恢復(fù)。
一旦你的程序可以從與錯誤相關(guān)的異常中恢復(fù),應(yīng)該先檢查程序的狀態(tài)和它的文檔。如果程序和文檔已經(jīng)被破壞了,進(jìn)程也應(yīng)該終止運(yùn)行。否則,程序需要通知客戶機(jī)確定動作的過程。如果客戶機(jī)同意執(zhí)行下去,程序應(yīng)該恢復(fù)錯誤并繼續(xù)執(zhí)行。
四、返回值
并不是在所以場合下都能使用異常,如在使用Windows API編程或帶有COM編程時并不使用異常。在異常不適合的時候,使用返回值是一個好的辦法。
返回值的基本特性:
.返回值可以指示正常和不正常的函數(shù)運(yùn)行,但不能阻止線程的繼續(xù)運(yùn)行。
.返回值很容易被忽略。
.返回值在典型情況下是一個整數(shù),通常映射符合于一個預(yù)定義的值。
.返回值能高效地傳遞和接收。
因此,返回值最適合用于以下的情形:
.用于非錯誤的狀態(tài)信息
.用于大多數(shù)情況下可以隨意忽略而不會出問題的錯誤。
.用于更易于出現(xiàn)在循環(huán)中的錯誤。
.用于中間語言模塊如COM組件中的錯誤。
使用Visual C++調(diào)試器調(diào)試
一、調(diào)試版本與發(fā)布版本
有時程序能在調(diào)試版本運(yùn)行但不能運(yùn)行于發(fā)布版本,反之也有可能。一般說來,一個發(fā)布版本意味著某些類型的優(yōu)化,而一個調(diào)試版本則沒有優(yōu)化。下面我們來看看它們的區(qū)別:
1、特別針對調(diào)試版本的編譯選項(xiàng)
(1)/MDd,/MLd或者/MTd
調(diào)試版本的運(yùn)行時刻庫有調(diào)試符號,使用了調(diào)試堆,調(diào)試堆的目的是發(fā)現(xiàn)內(nèi)存破壞和內(nèi)存泄漏,并且向用戶報告源代碼的哪個地方出了問題。特性:
.調(diào)試版本的運(yùn)行時刻庫對內(nèi)存的分配作了跟蹤,允許用戶檢查內(nèi)存泄漏。
.在剛分配的內(nèi)存里寫上0xCD的字節(jié)模式,用0xCD來填充剛分配的內(nèi)存,有助于發(fā)現(xiàn)數(shù)據(jù)未被初始化的錯誤。
.在被釋放的內(nèi)存寫上0xDD的字節(jié)模式,有助于發(fā)現(xiàn)已被釋放的內(nèi)存。
.在緩沖區(qū)的兩邊分配了四字節(jié)的保護(hù)數(shù)據(jù),并用0xFD的字節(jié)模式作初始化,來檢查寫內(nèi)存的上溢出和下溢出。
.在每個內(nèi)存分配的地方對源代碼文件名和行號作了記錄,有助于用戶在源代碼中對內(nèi)存分配進(jìn)行定位。
(2)/Od
這個選項(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è)是錯誤的,并且去掉的冗余也有可能隱藏錯 誤。如發(fā)布版本的幀指針(EBP寄存器)省略(FPO)隱藏了函數(shù)原型不匹配的錯誤;在同步異常模式(只能由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)。只有這個符號被定義,調(diào)試代碼才會被編譯,MFC使用_DEBUG符號來確定到底鏈接的是哪個版本的MFC類庫。在調(diào)試版本中,內(nèi)聯(lián)默認(rèn)情況下是被關(guān)閉的。
(4)/ZI
創(chuàng)建編輯繼續(xù)(Edit and Continue)的程序數(shù)據(jù)庫。這個選項(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)下面的提示信息 “One or more files are out of date or do not exist.”。
(5)/GZ
在調(diào)試版本中用來發(fā)現(xiàn)那些在發(fā)布版本里才發(fā)現(xiàn)的錯誤。其作用如下:
.用0xCC模式初始化自動(本地)變量。
.在通過函數(shù)指針調(diào)用函數(shù)時,檢查棧指針,確認(rèn)是否有調(diào)用規(guī)則不匹配。
.在函數(shù)最后檢查棧指針是否被改變。
(6)/Gm
打開最小化重新鏈接開關(guān),減少鏈接時間。
2、特別針對發(fā)布版本的編譯選項(xiàng)
(1)/MD,/ML或者/MT
使用發(fā)布版本的運(yùn)行時刻庫。
(2)/O1或者/O2
打開優(yōu)化開關(guān),使得程序會最小或說速度會最快,優(yōu)化器還可能發(fā)現(xiàn)代碼中潛在的錯誤,而這些錯誤可能會被調(diào)試版本掩蓋。
(3)/D “NDEBUG”
關(guān)閉條件編譯調(diào)試代碼開關(guān)。
(4)/GF
消除重復(fù)字符串并將它們放到只讀內(nèi)存中以避免被錯誤地修改。
(5)/Zi
創(chuàng)建包含調(diào)試符號的程序數(shù)據(jù)庫。
如果一個錯誤只發(fā)生在發(fā)布版本里,除非你是個匯編高手,否則你需要調(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ú)的一個PDB文件中。對于發(fā)布版本,選擇Link標(biāo)簽,在Project options對話框的最后加上“/OPT: REF”,這個選項(xiàng)使得不被引用的函數(shù)和數(shù)據(jù)不會出現(xiàn)在可執(zhí)行文件中,避免了文件的無謂增大。對于調(diào)試版本不要使用這個選項(xiàng),它會關(guān)閉增量鏈接 (incremental linking)。
二、Visual C++編輯器的“設(shè)置”菜單
當(dāng)你打開或新建一個包 含至少一個工程的Workspace后,Visual C++的Project菜單中的“Settings…”命令就變?yōu)橛行Вx擇它或者按下熱鍵Alt +F7后,便可調(diào)出工程設(shè)置對話框,這里面的選項(xiàng)將影響整個工程的建立和調(diào)試過程,因此很重要。
在這個對話框中,左上方的下拉列表框 用于選擇一種工程配置,包括有Win32 Debug、Win32 Release和All Configurations(指前兩種配置一起),某些選 項(xiàng)在不同的工程配置中有不同的缺省值。左邊的樹形視圖給出了當(dāng)前工程所有的文件及分類情況。下面我們就以Win32 Debug為例來看看與工程有關(guān)的的 四個主要選項(xiàng)卡的各自功能與含義(一共有十個選項(xiàng)卡):
1、 General選項(xiàng)卡
這個選項(xiàng)卡比較簡單,從上向下的 第一個選項(xiàng)用于更改使用MFC類庫的方式: DLL的方式或是靜態(tài)連接。我們可以在兩種方式之間進(jìn)行切換。第二個選項(xiàng)用于指定在編譯連接過程中生成的中間 文件和輸出文件的存放目錄,對于調(diào)試版本來說,缺省的目錄是工程下面的“Debug”子目錄。第三個選項(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)比較多,它們被分成了幾個類,我們可以從Category中選擇不同的類別,選項(xiàng)卡就會切換顯示出相應(yīng)的選項(xiàng)。
在General類別中,可以指定要調(diào)試的可執(zhí)行文件名。另外三個選項(xiàng)可以指定用于調(diào)試的工作目錄,開始調(diào)試時給程序傳送的命令行參數(shù),以及進(jìn)行遠(yuǎn)程調(diào)試時可執(zhí)行文件的路徑。
3、C/C++選項(xiàng)卡
C/C++選項(xiàng)卡控制著Visual C++的編譯器,其中的選項(xiàng)比較多。下面有一個Project Options編輯框,里面 列出的各種命令開關(guān)將會在開始編譯時作為命令行參數(shù)傳送給Visual C++的編譯器。這些命令開關(guān)會跟隨其它選項(xiàng)改變而改變。
在General類別中,Warning level用于指定編譯器顯示警告的級別,如果選中了Warnings as errors,那么顯示的每一 個警告都將會引起一個錯誤,這樣在編譯完畢后就無法啟動連接器來進(jìn)行連接。Optimizations用于設(shè)置代碼優(yōu)化方式,優(yōu)化的目的主要有提高運(yùn)行速 度和減小程序體積兩種,但有時候這兩種目的是相互矛盾的。另外,在極少數(shù)情況下,不進(jìn)行優(yōu)化,程序能正常運(yùn)行,打開了優(yōu)化措施之后,程序卻會出現(xiàn)一些莫名 其妙的問題。其實(shí)這多半是程序中有潛在的錯誤,關(guān)閉優(yōu)化措施往往只是暫時解決問題。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)行時類型信息,一般情況下都不用改變它們。Code Generation類別中的選項(xiàng)涉 及如何生成目標(biāo)代碼,一般情況下保持缺省值即可。在Customize類別中,從上到下六個選項(xiàng)的含義分別為:是否禁止使用Microsoft對C++的 擴(kuò)展;是否允許函數(shù)級別的連接;是否消除重復(fù)的字符串;是否允許進(jìn)行最小化的重建;是否允許遞增編譯方式;是否允許編譯器在開始運(yùn)行時向Output窗口 中輸出自己的版本信息。
在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)文件,下邊五個選項(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)試時速度卻會慢一些。Input類別中是一些與輸入庫文件有關(guān)的選項(xiàng),我們可以在這里指定使用或不使用某些庫文件或目標(biāo)文件。 Output類別中則是一些與最終輸出的可執(zhí)行文件有關(guān)的選項(xiàng),一般情況下都不用改變。
三、Visual C++調(diào)試工具
1、調(diào)試窗口
(1)觀察窗口(Watch)
調(diào)試程序時,可使用觀察窗口監(jiān)視變量和表達(dá)式。
(2)快速查看窗口(Quick watch)
功能和觀察窗口差不多。
(3)變量窗口(Variables)
變量窗口有三個標(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框允許你指定從哪個虛擬內(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)試器的查錯能力
盡量采用編譯時刻檢查而不是運(yùn)行時刻檢查。
1、使用最高的編譯警告級別/W4
象if(x=2)這樣的語句,默認(rèn)的警告級別為/W3時不顯示任何信息,但改成最高警告級別/W4時則會出現(xiàn)“waning C4706: assignment within conditional expression”的警告。/W4能給出一些/W3所不能給的警告。
2、在調(diào)試版本中使用/GZ編譯選項(xiàng)
/GZ選項(xiàng)用來發(fā)現(xiàn)那些在發(fā)布版本里才發(fā)現(xiàn)的錯誤,包括未被初始化的自動(局部)變量、堆棧錯誤、不正確的函數(shù)原型等。
3、使用#pragma warning編譯器指示
你可以使用#pragma warning編譯器指示來禁止整個程序、特定的頭文件、特定的代碼文件或是特定的某一行代碼的特定警告,這看你把#pragma放在哪里。
4、使用沒有警告的編譯法則/WX
這個編譯選項(xiàng)把所有的警告當(dāng)成錯誤來對待,只有在假警告被消除之后才能應(yīng)用。有時編譯警告可能是合理的,處理編譯警告的核心是要發(fā)現(xiàn)錯誤,而不是抑制警告本身。這個法則對于大的程序開發(fā)小組來說很有幫助。最終目標(biāo)是消除錯誤,而不是消除警告。
五、內(nèi)存空間與分配
1、內(nèi)存分配錯誤
動態(tài)內(nèi)存分配錯誤有兩種基本類型:內(nèi)存錯誤和內(nèi)存泄漏。
(1)內(nèi)存錯誤
當(dāng)一個指針或者該指針?biāo)赶虻膬?nèi)存單元成為無效單元,或者內(nèi)存中分配的數(shù)據(jù)結(jié)構(gòu)被破壞時,就會造成內(nèi)存錯誤。指針未被初始化,指針被初始化為一個無效地 址,指針被不小心錯誤地修改,在與指針相關(guān)聯(lián)的內(nèi)存區(qū)域被釋放后使用該指針(這種指針被稱為虛懸(dangling)指針),這些都會使指針變?yōu)闊o效指 針。當(dāng)通過一個錯誤指針或者虛懸指針對內(nèi)存進(jìn)行寫入,或者將指針強(qiáng)制轉(zhuǎn)換為不匹配的數(shù)據(jù)結(jié)構(gòu),又或者是寫數(shù)據(jù)越界,內(nèi)存自身也會遭到破壞。刪除未被初始化 的指針、刪除非堆指針、多次刪除同一指針或者覆蓋一個指針的內(nèi)部數(shù)據(jù)結(jié)構(gòu),都會造成內(nèi)存分配系統(tǒng)錯誤。
(2)內(nèi)存泄漏
內(nèi)存泄漏在被動態(tài)分配的內(nèi)存沒有被釋放時產(chǎn)生。有許多情況會導(dǎo)致內(nèi)存泄漏,如沒有在程序的全部執(zhí)行路徑中釋放內(nèi)存,沒有在析構(gòu)函數(shù)中釋放所有的內(nèi)存等。一個程序在崩潰之前可運(yùn)行的時間越長,則導(dǎo)致崩潰的原因與內(nèi)存泄漏的關(guān)系越大。
Windows會在程序結(jié)束的時候?qū)⑿孤┑膬?nèi)存收回,因此內(nèi)存泄漏是個暫時性的問題。但為什么必須消除內(nèi)存泄露呢?首先,內(nèi)存泄漏往往會導(dǎo)致系統(tǒng)資源的 泄漏。動態(tài)分配內(nèi)存往往不僅僅代表一塊存儲區(qū)域,還代表了某些類型的系統(tǒng)資源,如文件、窗口、設(shè)備上下文、GDI對象等。其次,高質(zhì)量的程序和特定的服務(wù) 器程序必須能夠無限地運(yùn)行下去。最后,內(nèi)存泄漏往往是其他程序錯誤或不良編程習(xí)慣的征兆。
導(dǎo)致內(nèi)參泄漏的原因:忘記釋放內(nèi)存;構(gòu)造函數(shù)失敗;存在內(nèi)存泄漏的析構(gòu)函數(shù);存在內(nèi)存泄漏的異常處理程序;多個返回語句;使用錯誤形式的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虛擬地址空間,因此有時可通過查看指針的返回值來判斷指針是否有效。
(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:堆(有時堆位于此處)
0x00400000~0x005FFFFF:可執(zhí)行代碼
0x00600000~0x0FFFFFFF:堆(有時堆位于此處)
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)橐粋€內(nèi)部編譯器錯誤而失敗時,最好刪除工程中的Debug或Release文件夾,從頭開始重新進(jìn)行編連。
(2)重新啟動Visual C++
Visual C++有超強(qiáng)的能力,但編譯器的某些特性也會引起奇怪的錯誤。如果你的程序表現(xiàn)得很奇怪,你可是試著清除所有的斷點(diǎn),關(guān)閉或隱藏觀察窗 口,檢查工程設(shè)置對話框看最近做了什么修改,直至重新啟動Visual C++以便消除由于Visual C++環(huán)境引起的異常行為。
(3)重新啟動Windows
當(dāng)你發(fā)現(xiàn)Windows或者其他程序表現(xiàn)出異常的或出人意料的行為時,就應(yīng)該重新啟動Windows,以消除操作系統(tǒng)給調(diào)試帶來的干擾。