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 */
从中也可以看出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 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)) || \
( != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \
(_CrtDbgBreak(), )) #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。
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)))
最终调用__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) && \
( == _CrtDbgReport(_CRT_ASSERT, (__FILE__), __LINE__, \
NULL, #expr##"\nProblem: "##expr_desc))) \
_CrtDbgBreak();}while()
#endif
仅供参考的代码,功能相对比较简单。
四、断言使用准则(推荐)
1. 什么情况下需要使用断言?
断言仅用于检查有效性,而不是正确性。也就是说即使程序有错误,断言要做的是发现错误,在错误经过断言时能够报告程序中有错误,而不是报告程序不正确。断言是给开发人员使用的代码诊断的有效工具。断言通常用于以下几种情况:
- 检查函数输入(前置条件)。验证函数参数、成员变量、全局变量,以及其他可能使用的变量状态。
- 检查函数输出(后置条件)。特别是在经过特殊而复杂的处理逻辑之后,检查输出参数、对象成员的有效性。
- 检查对象当前状态。比如对象是否被正常初始化、当前状态是否允许指定函数调用或者输入。
- 检查逻辑变量的合理性和一致性。包括循环不变量、计数器、偏移变量、不可能出现的值、不可能出现的情况以及某些固定的关系等。
- 检查类的不变量。
2. 什么情况下不能使用断言?
- 断言不能够检查哪些可能正确也可能错误的情况。正确运行的程序不会使任何断言生效,如果发生断言失效的情况,则程序一定存在某种错误。
- 断言不是防御性编程的替代品。断言不能防止程序的发布版本崩溃。
- 断言不能检查哪些不是实现方面的错误。这些非实现方面的错误包括用户输入错误、资源分配错误、文件系统错误以及硬件错误。断言不是异常处理、返回值或其他形式的错误处理的替代品。
- 断言不能够向用户报告错误。
- 断言不能不能包含程序代码,不能有副作用。断言不能修改变量,也不能调用修改程序变量的函数。
Windows下断言的类型及实现的更多相关文章
- Linux、Windows 下手动生成 sha256 等类型的校验文件
目录 1 - 校验文件的作用 2 - Linux 下生成校验文件 3 - Windows 下生成校验文件 参考资料 版权声明 1 - 校验文件的作用 从网服务器下载文件,尤其是比较大的文件时,很容易由 ...
- windows下捕获dump之Google breakpad_client的理解
breakpad是Google开源的一套跨平台工具,用于dump的处理.很全的一套东西,我这里只简单涉及breakpad客户端,不涉及纯文本符号生成,不涉及dump解析. 一.使用 最简单的是使用进程 ...
- PhantomJS与CasperJS在Windows下的安装与使用
按照网上的教程来呢,一定是不好使的,这是常理. 所以必须要告诉你怎么使用Phantomjs…… 这么用! 1.下载Phantomjs的压缩包并解压缩: 2.在bin目录(包含phantomjs.exe ...
- windows下捕获dump之Google breakpad_client
breakpad是Google开源的一套跨平台工具,用于dump的处理.很全的一套东西,我这里只简单涉及breakpad客户端,不涉及纯文本符号生成,不涉及dump解析. 一.使用 最简单的是使用进程 ...
- windows下获取IP地址的两种方法
windows下获取IP地址的两种方法: 一种可以获取IPv4和IPv6,但是需要WSAStartup: 一种只能取到IPv4,但是不需要WSAStartup: 如下: 方法一:(可以获取IPv4和I ...
- 原创 C++应用程序在Windows下的编译、链接:第三部分 静态链接(二)
3.5.2动态链接库的创建 3.5.2.1动态链接库的创建流程 动态链接库的创建流程如下图所示: 在系统设计阶段,主要的设计内容包括:类结构的设计以及功能类之间的关系,动态链接库的接口.在动态链接库中 ...
- 原创 C++应用程序在Windows下的编译、链接:第一部分 概述
本文是对C++应用程序在Windows下的编译.链接的深入理解和分析,文章的目录如下: 我们先看第一章概述部分. 1概述 1.1编译工具简介 cl.exe是windows平台下的编译器,link.ex ...
- Windows下LATEX排版论文攻略—CTeX、JabRef使用介绍
Windows下LATEX排版论文攻略—CTeX.JabRef使用介绍 一.工具介绍 TeX是一个很好排版工具,在学术界十分流行,特别是数学.物理学和计算机科学界. CTeX是TeX中的一个版本,指的 ...
- C语言的可变参数在Linux(Ubuntu)与Windows下注意点
基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...
随机推荐
- CreateDialog 注意事项
CreateDialog创建非模态对话框时 其内部 会发送几条消息例如: WM_INITDIALOG,WM_SETFONT DS_SETFONT , DS_SHELLFONT. 所以如果在另一个Ca ...
- 【LeetCode】201. Bitwise AND of Numbers Range
Bitwise AND of Numbers Range Given a range [m, n] where 0 <= m <= n <= 2147483647, return ...
- nginx 中文和英文资料
http://www.nginx.cn/doc/ http://manual.51yip.com/nginx/ http://tool.oschina.net/apidocs/apidoc?api=n ...
- Nginx Rewrite正则表达式案例
前两天简单整理了下Nginx的URL Rewrite基本指令,今天谈谈Nginx Rewrite的location正则表达式. 1.Nginx Rewrite 基本标记(flags) last 相当于 ...
- Linux下DIR,dirent,stat等结构体详解(转)
最近在看Linux下文件操作相关章节,遇到了这么几个结构体,被搞的晕乎乎的,今日有空,仔细研究了一下,受益匪浅. 首先说说DIR这一结构体,以下为DIR结构体的定义: struct __dirstre ...
- JAXBContext处理CDATA
今天做Lucene数据源接口时,遇到一个问题,就是输出xml时将某些数据放在CDATA区输出: 1.依赖的jar包,用maven管理项目的话, <dependency> <group ...
- Android开发 Fragment中调用startActivityForResult返回错误的requestCode
返回错误的requestCode返回值为65537,在Fragment里调用startActivityForResult,就必须在Fragment里处理onActivityResult.
- Android逆向之旅---破解"穿靴子的猫"游戏的收费功能
一.游戏收费分析 游戏收费非常正常的,可是玩游戏最恶心的就是你还没玩就要充值,非常恼火,事实上我不怎么玩游戏,主要是给小孩子们弄,比方如今好多小屁孩们喜欢玩水果忍者这个游戏.可是这个游戏在没有開始玩的 ...
- java基础知识总结(二)
+=隐含了强制类型转换. x+=y;等价与:x = (x的数据类型)(x + y); 函数重载? 函数名同样.參数列表不同.跟返回值不关,就是函数重载 封装是什么? 隐藏对象的属性和详细的实现细节,仅 ...
- PowerShell 批量签入SharePoint Document Library中的文件
由于某个文档库设置了编辑前签出功能,导致批量导入文件时这些文件默认的状态都被签出了.如果手动签入则费时费力,故利用PowerShell来实现批量签入Document Library中的文件. Reso ...