判斷提示
判斷提示陳述式指定您希望成立在程式的中斷點的條件。如果該條件不成立,判斷提示失敗,程式執行將會中斷,,且會出現 判斷提示失敗對話方塊 。
Visual C++ 支援根據下列建構的判斷提示陳述式:
您可以使用判斷提示來尋找邏輯錯誤,請檢查作業和應該處理的測試錯誤條件的結果。
本主題內容
判斷提示的運作方式
判斷提示偵錯和發行組建
使用判斷提示副作用
CRT 判斷提示
MFC 判斷提示
MFC ASSERT_VALID 和 CObject::AssertValid
AssertValid 的限制
使用判斷提示
攔截邏輯錯誤。
檢查結果
尋找未處理錯誤。
判斷提示的運作方式
當偵錯工具中止由於 MFC 或 C 執行階段程式庫判斷提示時,然後,如果來源可用,偵錯工具巡覽至判斷提示產生的原始程式檔的點。判斷提示訊息會出現在 輸出視窗 和 [失敗的判斷提示] 對話方塊。如果您要儲存判斷提示訊息以供未來參考,您可以從 [輸出] 視窗複製這段內容至文字視窗。[輸出] 視窗也可能包含其他的錯誤訊息。請仔細地檢查這些訊息,因為它們提供了造成判斷提示失敗的線索。
在開發期間,使用 Assert 偵測錯誤。通常,為每個假設使用判斷提示。例如,在中,如果您假設,引數不是空的,請使用判斷提示來測試該假設。
本主題內容
判斷提示偵錯和發行組建
只有當 _DEBUG 定義,判斷提示陳述式來編譯。否則,編譯器會將判斷提示做為 null 陳述式。因此,判斷提示陳述式不會加入額外負荷和效能成本在最終發行程式,可讓您避免使用 #ifdef 指示詞。
使用判斷提示副作用
在程式碼加入判斷提示時,請確定判斷提示不會有副作用。例如,請考慮修改 nM 值的下列判斷提示:
ASSERT(nM++ > 0); // Don't do this!
由於 ASSERT 運算式在程式的發行版本中, nM 會在偵錯版本和發行版本的不同的值。若要避免在 MFC 的問題,您可以使用 驗證 巨集取代 ASSERT。VERIFY 會在任何版本的運算式,但不會檢查發行版本的結果。
請特別小心在判斷提示陳述式裡使用函式呼叫 (Function Call),因為評估函式可能會有意料之外的影響。
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe
VERIFY 會在兩個偵錯版本和發行版本的 myFnctn ,因此,使用是可接受的。不過,使用 VERIFY 加上必要函式呼叫的額外負荷在發行版本中。
本主題內容
CRT 判斷提示
CRTDBG.H 標頭檔 (Header File) 會定義檢查判斷提示的 _ASSERT 和 _ASSERTE 巨集。
巨集 |
結果 |
---|---|
_ASSERT |
如果指定的運算式評估結果是 FALSE,結果會是 _ASSERT 的檔名和行號。 |
_ASSERTE |
與 _ASSERT 狀況相同,加上要判斷提示的運算式之字串表示。 |
_ASSERTE 功能較強大,因為它會報告結果為 FALSE 的判斷提示運算式。這點足以在無須參考原始程式碼情況下辨識問題。然而,應用程式的偵錯版本會包含每個使用 _ASSERTE 的判斷提示運算式的字串常數。如果您使用許多 _ASSERTE 巨集,這些字串運算式 (String Expression) 會需要相當多的記憶體。如果這證實會變成一種問題,請使用 _ASSERT 以節省記憶體。
當 _DEBUG 後, _ASSERTE 巨集定義如下:
#define _ASSERTE(expr) \
do { \
if (!(expr) && (1 == _CrtDbgReport( \
_CRT_ASSERT, __FILE__, __LINE__, #expr))) \
_CrtDbgBreak(); \
} while (0)
如果判斷提示運算式結果為 FALSE,則會呼叫 _CrtDbgReport 以報告判斷提示失敗 (使用預設的訊息對話方塊)。如果您選擇訊息對話方塊中的 [重試] ],則 _CrtDbgReport 會傳回 1,而 _CrtDbgBreak 透過 DebugBreak呼叫偵錯工具。
檢查堆積損毀
下列範例使用 _CrtCheckMemory 來檢查堆積的損毀:
_ASSERTE(_CrtCheckMemory());
檢查指標有效性
下列範例使用 _CrtIsValidPointer 來驗證指定記憶體範圍對於讀取或撰寫是否有效。
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
下列範例使用 _CrtIsValidHeapPointer 來驗證指向本機堆積裡記憶體的指標 (這是指由 C 執行階段程式庫的執行個體 (Instance) 建立和管理的堆積 ─ 也就是 DLL 可以有它自己的程式庫執行個體,因此它自己的堆積是在應用程式堆積的外部)。這個判斷提示不僅可捕捉 Null 或超出範圍的位址,還可捕捉靜態變數、堆疊變數和任何其他非本機記憶體的指標。
_ASSERTE(_CrtIsValidPointer( myData );
檢查記憶體區塊
下列範例使用 _CrtIsMemoryBlock 來驗證記憶體區塊是本機堆積且擁有有效的區塊類型。
_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));
本主題內容
MFC 判斷提示
MFC 會定義檢查判斷提示的 ASSERT 巨集。它也會定義檢查 CObject的內部狀態衍生物件 MFC ASSERT_VALID 和 CObject::AssertValid 方法。
如果 ASSERT MFC 巨集評估的引數為零或 false,巨集來暫止程式執行並警告使用者;否則,執行會繼續。
當判斷提示失敗時,訊息對話方塊顯示原始程式檔的名稱和判斷提示的行號。如果您選擇了對話方塊裡的 [重試],則呼叫 AfxDebugBreak 會造成偵錯工具執行中斷。此時,您可以檢查呼叫堆疊和使用其他偵錯工具設施判斷提示的原因。如果您已啟用 Just-In-Time 偵錯,因此,偵錯工具尚未執行,對話方塊會啟動偵錯工具。
下列範例示範如何使用 ASSERT 來檢查函式的傳回值:
int x = SomeFunc(y);
ASSERT(x >= 0); // Assertion fails if x is negative
您可以使用 ASSERT 搭配 IsKindOf 函式來提供函式引數的類型檢查:
ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );
ASSERT 巨集不會導致在發行版本的程式碼。如果您需要評估發行版本裡的運算式,請使用 VERIFY 巨集取代 ASSERT。
MFC ASSERT_VALID 和 CObject::AssertValid
CObject::AssertValid 方法提供物件內部狀態的執行階段檢查。雖然從 CObject 衍生類別時不需要覆寫 AssertValid,但這樣做可以讓您的類別更可靠。AssertValid 應該執行所有的判斷提示物件的成員變數驗證它們包含有效值。例如,它應該檢查指標成員變數不是 NULL。
下面的範例顯示如何宣告 AssertValid 函式:
class CPerson : public CObject
{
protected:
CString m_strName;
float m_salary;
public:
#ifdef _DEBUG
// Override
virtual void AssertValid() const;
#endif
// ...
};
當您覆寫 AssertValid 時,請在執行您自己的檢查之前呼叫 AssertValid 的基底類別 (Base Class) 版本。然後使用 ASSERT 巨集來檢查您的衍生類別 (Derived Class) 唯一的成員,如同下列所示:
#ifdef _DEBUG
void CPerson::AssertValid() const
{
// Call inherited AssertValid first.
CObject::AssertValid();
// Check CPerson members...
// Must have a name.
ASSERT( !m_strName.IsEmpty());
// Must have an income.
ASSERT( m_salary > 0 );
}
#endif
如果您的任何成員變數儲存物件,您可以使用 ASSERT_VALID 巨集來測試它們的內部有效性 (如果它們的類別覆寫了 AssertValid)。
例如,參考 CMyData 類別,它會在其中一個成員變數裡儲存 CObList。CObList 變數,m_DataList,會儲存 CPerson 集合物件 (Collection)。CMyData 的縮寫宣告看起來像這樣:
class CMyData : public CObject
{
// Constructor and other members ...
protected:
CObList* m_pDataList;
// Other declarations ...
public:
#ifdef _DEBUG
// Override:
virtual void AssertValid( ) const;
#endif
// And so on ...
};
CMyData 裡的 AssertValid 覆寫看起來像這樣:
#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
// Call inherited AssertValid.
CObject::AssertValid( );
// Check validity of CMyData members.
ASSERT_VALID( m_pDataList );
// ...
}
#endif
CMyData 使用 AssertValid 機制來測試儲存在它的資料成員裡之物件有效性。CMyData 的覆寫 AssertValid 叫用它自己的 m_pDataList 成員變數之ASSERT_VALID 巨集。
因為 CObList 類別也會覆寫 AssertValid,有效性測試在這個階層架構不會停止。這個覆寫會對清單的內部狀態執行額外的有效性測試。因此,CMyData 物件的有效性測試會變成儲存 CObList 清單物件額外的內部狀態有效性測試。
透過一些工作,您也可以為儲存在清單裡的 CPerson 物件加入有效性測試。您可以從 CObList 衍生 CPersonList 類別並且覆寫 AssertValid。您可以在此覆寫中呼叫 CObject::AssertValid,然後逐一查看清單,進而呼叫儲存於清單中的一個 CPerson 物件上的 AssertValid。本主題開頭顯示的 CPerson 類別已經覆寫 AssertValid。
這在建置偵錯時,是一項功能強大的機制。當您接下來建置發行時,此機制會自動關閉。
AssertValid 的限制
觸發判斷提示表示物件一定是壞的,且會停止執行。不過,缺乏判斷提示表示只找不到問題,不過,物件並不保證是好。
本主題內容
使用判斷提示
攔截邏輯錯誤。
您可以在根據程式邏輯必須是 True 的條件上設定判斷提示。判斷提示不會有影響除非有邏輯錯誤發生。
例如,假設您正在模擬一個容器 (Container) 裡的氣體分子數,變數 numMols 表示分子的總數。這個數字不能小於零,因此您可以包含像這樣的 MFC 判斷提示陳述式:
ASSERT(numMols >= 0);
或者,您也可以包含像這樣的 CRT 判斷提示:
_ASSERT(numMols >= 0);
如果您的程式正常操作,這些陳述式不會做任何事。如果邏輯錯誤造成 numMols小於零,然而,判斷提示暫停程式的執行並顯示 判斷提示已失敗對話方塊。
本主題內容
檢查結果
判斷提示的結果會以一次快速視覺檢視不明顯測試作業有值。
例如,參考下列程式碼,它會根據 mols 所指的連結串列 (Linked List) 內容來更新變數 iMols:
/* This code assumes that type has overloaded the != operator
with const char *
It also assumes that H2O is somewhere in that linked list.
Otherwise we'll get an access violation... */
while (mols->type != "H2O")
{
iMols += mols->num;
mols = mols->next;
}
ASSERT(iMols<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version
由 iMols 所計算的分子數一定會小於或等於分子總數 (即 numMols)。迴圈的視覺檢視不會顯示這是種必須狀況,因此判斷提示陳述式是用於迴圈之後來測試該條件。
本主題內容
尋找未處理錯誤。
您可以在程式碼中任何需要處理錯誤的地方,使用判斷提示來測試錯誤條件。在下列範例裡,一個圖形常式會傳回錯誤碼,或在成功時傳回零。
myErr = myGraphRoutine(a, b);
/* Code to handle errors and
reset myErr if successful */
ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version
如果錯誤處理程式碼正常運作,錯誤應該會處理而且在到達判斷提示之前,myErr 會重設為零。如果 myErr 有另一個值,則判斷提示會失敗、程式會暫止,而且判斷提示已失敗對話方塊會出現。
然而,判斷提示陳述式不會用來替代錯誤處理的程式碼。下列範例顯示一個會在最後發行程式碼裡造成問題的判斷提示陳述式:
myErr = myGraphRoutine(a, b);
/* No Code to handle errors */
ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!
這段程式碼依賴判斷提示陳述式來處理錯誤狀況。因此,最後發行程式碼不會處理任何由 myGraphRoutine 傳回的錯誤碼。
本主題內容