C/C++ 判斷提示
判斷提示陳述式會指定預期在程式中某個時間點為 true 的條件。 如果該條件並非為 true,表示判斷提示失敗,程式執行將會中斷,並出現 [判斷提示失敗] 對話方塊。
Visual Studio 支援以下列建構為基礎的 C++ 判斷提示陳述式:
MFC 程式的 MFC 判斷提示。
ATLASSERT 適用於使用 ATL 的程式。
使用 C 執行時間程式庫之程式的 CRT 判斷提示。
其他 C/C++ 程式的 ANSI assert 函式。
您可以使用判斷提示來擷取邏輯錯誤、檢查作業的結果,以及應該處理的測試錯誤條件。
本主題內容
判斷提示的運作方式
當偵錯工具因為 MFC 或 C 執行階段程式庫判斷提示而停止時,如果來源可用,偵錯工具就會巡覽至發生判斷提示的來源檔案中的點。 判斷提示訊息會出現在 [輸出] 視窗 和 [判斷提示失敗] 對話方塊中。 如果您想要將判斷提示訊息從 [輸出] 視窗複製到文字視窗,以供日後參考。 [輸出] 視窗也可能包含其他錯誤訊息。 仔細檢查這些訊息,因為它們會提供判斷提示失敗原因的線索。
使用判斷提示來偵測開發期間的錯誤。 依規則,針對每個假設使用一個判斷提示。 例如,如果您假設引數不是 Null,請使用判斷提示來測試該假設。
偵錯和發行組建中的判斷提示
只有在已定義時 _DEBUG
,判斷提示陳述式才會編譯。 否則,編譯器會將判斷提示視為 Null 陳述式。 因此,判斷提示陳述式不會在最終發行程式中造成任何額外負荷或效能成本,並可讓您避免使用 #ifdef
指示詞。
使用判斷提示的副作用
當您將判斷提示新增至程式碼時,請確定判斷提示沒有副作用。 例如,請考慮修改 nM
值的下列判斷提示:
ASSERT(nM++ > 0); // Don't do this!
因為 ASSERT
運算式不會在程式的發行版本中評估,因此 nM
在偵錯和發行版本中會有不同的值。 若要避免 MFC 中的這個問題,您可以使用 VERIFY 巨集,而不是 ASSERT
。 VERIFY
會評估所有版本中的運算式,但不會檢查發行版本的結果。
請特別小心在判斷提示陳述式中使用函式呼叫,因為評估函式可能會有非預期的副作用。
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe
VERIFY
在偵錯和發行版本中呼叫 myFnctn
,因此可以使用。 不過,使用 VERIFY
會強加發行版本本中不必要的函式呼叫額外負荷。
CRT 判斷提示
CRTDBG.H
標頭檔會定義 _ASSERT
和 _ASSERTE
巨集以進行判斷提示檢查。
Macro | 結果 |
---|---|
_ASSERT |
如果指定的運算式評估為 FALSE,則為 _ASSERT 的檔案名稱和行號。 |
_ASSERTE |
與 _ASSERT 相同,加上判斷提示之運算式的字串表示。 |
_ASSERTE
功能更強大,因為它會報告原來為 FALSE 的判斷提示運算式。 這可能足以識別問題,而不參考原始程式碼。 不過,應用程式的偵錯版本將會針對使用 _ASSERTE
判斷提示的每個運算式包含字串常數。 如果您使用許多 _ASSERTE
巨集,這些字串運算式會佔用大量的記憶體。 如果這證明是個問題,請使用 _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
呼叫偵錯工具。
如果您需要暫時停用所有判斷提示,請使用 _CtrSetReportMode。
檢查堆積損毀
下列範例會使用 _CrtCheckMemory 來檢查堆積的損毀:
_ASSERTE(_CrtCheckMemory());
檢查指標有效性
下列範例會使用 _CrtIsValidPointer 來確認指定的記憶體範圍是否有效,以便讀取或寫入。
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
下列範例會使用 _CrtIsValidHeapPointer 來驗證指向本機堆積中記憶體的指標 (C 執行階段程式庫執行個體所建立和管理的堆積 - DLL 可以有自己的程式庫執行個體,因此在應用程式堆積之外有自己的堆積)。 這個判斷提示不僅會攔截 Null 或超出界限的位址,也會攔截靜態變數、堆疊變數,以及任何其他非局部記憶體的指標。
_ASSERTE(_CrtIsValidHeapPointer( myData );
檢查記憶體區塊
下列範例會使用 _CrtIsMemoryBlock 來確認記憶體區塊位於本機堆積中,且具有有效的區塊類型。
_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));
MFC 判斷提示
MFC 會定義 ASSERT 巨集以進行判斷提示檢查。 它也會定義 MFC ASSERT_VALID
和 CObject::AssertValid
方法,以檢查 CObject
衍生物件的內部狀態。
如果 MFC ASSERT
巨集的引數評估為零或 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
的基底類別,再執行自己的檢查。 然後使用 ASSERT 巨集來檢查衍生類別中唯一的成員,如下所示:
#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
物件的集合。 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。 除非發生邏輯錯誤,否則判斷提示沒有任何作用。
例如,假設您正在模擬容器中的氣體分子,而變數 numMols
代表分子總數。 此數位不能小於零,因此您可能會包含 MFC 判斷提示陳述式,如下所示:
ASSERT(numMols >= 0);
或者,您可能會包含 CRT 判斷提示,如下所示:
_ASSERT(numMols >= 0);
如果您的程式正常運作,這些陳述式不會執行任何動作。 不過,如果邏輯錯誤導致 numMols
小於零,判斷提示會停止執行您的程式,並顯示 [判斷提示失敗對話方塊]。
檢查結果
判斷提示對於從快速視覺檢查中看不出其結果的測試作業而言十分有用。
例如,請考慮下列程式碼,它會根據 mols
所指向的連結清單內容來更新變數 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
所傳回的任何錯誤碼都會在最終發行碼中取消處理。