Windows下断言的类型及实现
一、內容綜述
本文主要介紹Windows下斷言assert的實現,并總結斷言的不同應用準則。最后給出一個windows自定義斷言的方法。
本文行文參考《Debugging Windows Programs》第三章相關內容,如果有興趣的話建議讀者可以深入閱讀下。
?
二、斷言的類型
?1. ?ANSI C 斷言
?ANSI C斷言指的是assert函數,按照標準定義,assert是支持跨平臺的,原型如下:
void assert (int expression);C語言標準中規定,編譯器實現的斷言失敗消息至少包含以下信息:
Assertion failed:?expression(斷言失敗的參數), file?filename(源文件名稱), line?line number(斷言失敗的行號)。
另外,C語言標準中支持,在包含<assert.h>或<cassert>頭文件之前定義NDEBUG宏,可禁用assert函數的斷言判斷。
更加詳細的信息可參考:http://www.cplusplus.com/reference/cassert/assert/
Windows下assert函數,在控制臺程序和基于Windows的程序下彈窗差別較大。比如在我的pc下(Win7,vs2010,debug編譯),控制臺程序使用stderr顯示斷言消息,并彈出Debug Error的窗口。
?而在MFC中彈窗如下:
?二者斷言失敗的表現不太相同,但均符合C語言標準的規定。
從上面兩個截圖也可以看出,當源文件路徑比較長時,assert斷言的消息提示會把文件路徑截斷為短路徑格式,這樣很不容易定位出問題的代碼。因此,在Windows下不推薦直接使用assert斷言函數。
其他更詳細的assert信息,可參考:http://msdn.microsoft.com/en-us/library/9sb57dw4.aspx。
如下代碼給出了,vs2010中assert函數的頭文件聲明(assert.h頭文件)
#include <crtdefs.h>#undef assert#ifdef NDEBUG#define assert(_Expression) ((void)0)#else#ifdef __cplusplus extern "C" { #endif_CRTIMP void __cdecl _wassert(_In_z_ const wchar_t * _Message, _In_z_ const wchar_t *_File, _In_ unsigned _Line);#ifdef __cplusplus } #endif#define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )#endif /* NDEBUG */ View Code從中也可以看出NDEBUG宏為什么可以影響assert斷言了。
2. ?C-Runtime Library斷言
?C Runtime Library(CRT)提供了_ASSERT、_ASSERTE兩種形式的斷言,位于<crtdbg.h>頭文件中。_ASSERT、_ASSERTE函數僅在_DEBUG宏定義的情況下有效,這也是vs的debug版本中預定義宏_DEBUG的作用。
另外,這兩個函數在控制臺程序和基于Windows的應用程序下斷言失敗的提示消息框是一樣的。
_ASSERT會彈出如下所示的斷言失敗提示框
?_ASSERT斷言失敗提示框如下:
?
對比兩個對話框,_ASSERTE相比_ASSERT多了關于斷言失敗表達式的信息。這樣可以在不查看源代碼的情況下分析斷言失敗的原因,但是過多調用_ASSERTE會使debug版本的可執行程序中保存大量關于斷言失敗的表達式字符串,文件較大。
下面代碼是vs2010中crtdbg.h頭文件給出的關于_ASSERTE、_ASSERT的聲明,可以參考下
?
// 摘錄版本,只給出_ASSERT、_ASSERTE的相關部分 #ifndef _DEBUG /* We allow our basic _ASSERT macros to be overridden by pre-existing definitions. This is not the ideal mechanism, but is helpful in some scenarios and helps avoidmultiple definition problems */ #ifndef _ASSERT #define _ASSERT(expr) ((void)0) #endif #ifndef _ASSERTE #define _ASSERTE(expr) ((void)0) #endif#else /* Asserts */ /* We use !! below to ensure that any overloaded operators used to evaluate expr do not end up at operator || */ #define _ASSERT_EXPR(expr, msg) \(void) ((!!(expr)) || \(1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \(_CrtDbgBreak(), 0))#ifndef _ASSERT #define _ASSERT(expr) _ASSERT_EXPR((expr), NULL) #endif#ifndef _ASSERTE #define _ASSERTE(expr) _ASSERT_EXPR((expr), _CRT_WIDE(#expr)) #endif#if !defined(_CRT_PORTABLE) #define _CrtDbgBreak() __debugbreak() #else _CRTIMP void __cdecl _CrtDbgBreak(void); #endif#endif View Code?
如果有興趣深入了解,可以參考:http://msdn.microsoft.com/en-us/library/ezb1wyez.aspx。
3. MFC斷言
?MFC中,我用的最多的是VERIFY、ASSERT兩個斷言。
ASSERT和VERIFY斷言失敗的彈窗和_ASSERT相關,首先看下ASSERT、VERIFY的實現。代碼來自vs2010的相關頭文件
// from afxassert.cpp #ifdef _DEBUG // entire file// NOTE: in separate module so it can replaced if needed BOOL AFXAPI AfxAssertFailedLine(LPCSTR lpszFileName, int nLine) { #ifndef _AFX_NO_DEBUG_CRT// we remove WM_QUIT because if it is in the queue then the message box// won't display MSG msg;BOOL bQuit = PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);BOOL bResult = _CrtDbgReport(_CRT_ASSERT, lpszFileName, nLine, NULL, NULL);if (bQuit)PostQuitMessage((int)msg.wParam);return bResult; #else// Not supported. #error _AFX_NO_DEBUG_CRT is not supported. #endif // _AFX_NO_DEBUG_CRT } #endif // _DEBUG// from afxver_.h #ifndef AfxDebugBreak #define AfxDebugBreak() __debugbreak() #endif#ifndef _DEBUG #ifdef AfxDebugBreak #undef AfxDebugBreak #endif #define AfxDebugBreak() #endif // _DEBUG// from afx.h #ifdef _DEBUG BOOL AFXAPI AfxAssertFailedLine(LPCSTR lpszFileName, int nLine);#define THIS_FILE __FILE__ #define VERIFY(f) ASSERT(f) #define DEBUG_ONLY(f) (f)#else // _DEBUG#define VERIFY(f) ((void)(f)) #define DEBUG_ONLY(f) ((void)0)#endif // !_DEBUG#define ASSERT(f) DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0))) View Code最終調用__debugbreak(編譯器內部提供的函數)。
從上面的源碼中也可看出ASSERT和VERIFY的區別,_DEBUG宏定義的情況下二者完全功能和實現一樣;_DEBUG宏未定義的情況下,ASSERT將直接忽略斷言條件,而VERIFY會執行以下斷言條件語句。需要說明的是VERIFY也是斷言的一種,在Release模式下(通常_DEBUG宏不會定義)斷言是失效的,所以不推薦使用VERIFY用于判斷可能發生的錯誤,更進一步不推薦使用VERIFY,VERIFY對防御性編程有過多的誤導作用。
三、自定義斷言
?如果windows提供的斷言不能夠或者不適合與實際需要,可以自定義斷言的形式來提供更加自由的錯誤跟蹤體系,通常自定義斷言出于以下考慮
- 不同類型的程序提供可移植性,比如mfc、console、windows api、跨平臺需要。
- 簡化事后調試及問題跟蹤,在錯誤發生時提供調用堆棧。
- 不同的錯誤處理策略,比如不想看到系統彈出錯誤提示,而換用其他異常處理邏輯。
當然,也可能有其他要求。但是這里僅提供一種自定義斷言的方法,至于是否合適、是否需要重定義斷言還是需要讀者自己決定。通過上面第二部分關于斷言的介紹,顯然自己寫斷言處理并不復雜,可使用_CrtDbgReport和_CrtDgbBreak即可完成多數斷言工作。比如類似如下代碼的斷言實現:
// 多字節版本,使用Unicode需要修改調用函數 #ifndef _DEBUG #define VASSERT(expr, expr_desc) ((void)0) #else #define VASSERT(expr, expr_desc) \do { if (!(expr) && \(1 == _CrtDbgReport(_CRT_ASSERT, (__FILE__), __LINE__, \NULL, #expr##"\nProblem: "##expr_desc))) \_CrtDbgBreak();}while(0) #endif僅供參考的代碼,功能相對比較簡單。
?
四、斷言使用準則(推薦)
?1. 什么情況下需要使用斷言?
斷言僅用于檢查有效性,而不是正確性。也就是說即使程序有錯誤,斷言要做的是發現錯誤,在錯誤經過斷言時能夠報告程序中有錯誤,而不是報告程序不正確。斷言是給開發人員使用的代碼診斷的有效工具。斷言通常用于以下幾種情況:
- 檢查函數輸入(前置條件)。驗證函數參數、成員變量、全局變量,以及其他可能使用的變量狀態。
- 檢查函數輸出(后置條件)。特別是在經過特殊而復雜的處理邏輯之后,檢查輸出參數、對象成員的有效性。
- 檢查對象當前狀態。比如對象是否被正常初始化、當前狀態是否允許指定函數調用或者輸入。
- 檢查邏輯變量的合理性和一致性。包括循環不變量、計數器、偏移變量、不可能出現的值、不可能出現的情況以及某些固定的關系等。
- 檢查類的不變量。
2. 什么情況下不能使用斷言?
- 斷言不能夠檢查哪些可能正確也可能錯誤的情況。正確運行的程序不會使任何斷言生效,如果發生斷言失效的情況,則程序一定存在某種錯誤。
- 斷言不是防御性編程的替代品。斷言不能防止程序的發布版本崩潰。
- 斷言不能檢查哪些不是實現方面的錯誤。這些非實現方面的錯誤包括用戶輸入錯誤、資源分配錯誤、文件系統錯誤以及硬件錯誤。斷言不是異常處理、返回值或其他形式的錯誤處理的替代品。
- 斷言不能夠向用戶報告錯誤。
- 斷言不能不能包含程序代碼,不能有副作用。斷言不能修改變量,也不能調用修改程序變量的函數。
?
轉載于:https://www.cnblogs.com/tocy/p/4098245.html
總結
以上是生活随笔為你收集整理的Windows下断言的类型及实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF: 使用CommandManage
- 下一篇: Android获取屏幕实际高度跟显示高度