本文主要介绍Windows下断言assert的实现,并总结断言的不同应用准则。最后给出一个windows自定义断言的方法。
本文行文参考《Debugging Windows Programs》第三章相关内容,如果有兴趣的话建议读者可以深入阅读下。
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 */
从中也可以看出NDEBUG宏为什么可以影响assert断言了。
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 avoid multiple 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
如果有兴趣深入了解,可以参考:http://msdn.microsoft.com/en-us/library/ezb1wyez.aspx。
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)))
最终调用__debugbreak(编译器内部提供的函数)。
从上面的源码中也可看出ASSERT和VERIFY的区别,_DEBUG宏定义的情况下二者完全功能和实现一样;_DEBUG宏未定义的情况下,ASSERT将直接忽略断言条件,而VERIFY会执行以下断言条件语句。需要说明的是VERIFY也是断言的一种,在Release模式下(通常_DEBUG宏不会定义)断言是失效的,所以不推荐使用VERIFY用于判断可能发生的错误,更进一步不推荐使用VERIFY,VERIFY对防御性编程有过多的误导作用。
如果windows提供的断言不能够或者不适合与实际需要,可以自定义断言的形式来提供更加自由的错误跟踪体系,通常自定义断言出于以下考虑
当然,也可能有其他要求。但是这里仅提供一种自定义断言的方法,至于是否合适、是否需要重定义断言还是需要读者自己决定。通过上面第二部分关于断言的介绍,显然自己写断言处理并不复杂,可使用_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. 什么情况下不能使用断言?