Chinaunix首页 | 论坛 | 博客
  • 博客访问: 244767
  • 博文数量: 62
  • 博客积分: 973
  • 博客等级: 准尉
  • 技术积分: 530
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-16 23:25
文章分类

全部博文(62)

文章存档

2013年(1)

2012年(14)

2011年(47)

分类: C/C++

2011-12-08 13:28:09

assert宏的原型定义在中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
#include
void assert( int expression );

assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,
然后通过调用 abort 来终止程序运行。

请看下面的程序清单badptr.c:
#include
#include
#include

int main( void )
{
    FILE *fp;

    fp = fopen( "test.txt", "w" );//以可写的方式打开一个文件,如果不存在就创建一个同名文件
    assert( fp ); //所以这里不会出错
    fclose( fp );

    fp = fopen( "noexitfile.txt", "r" );//以只读的方式打开一个文件,如果不存在就打开文件失败
    assert( fp ); //所以这里出错
    fclose( fp ); //程序永远都执行不到这里来

    return 0;
}

[root@localhost error_process]# gcc badptr.c
[root@localhost error_process]# ./a.out
a.out: badptr.c:14: main: Assertion `fp'' failed.
已放弃

使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
在调试结束后,可以通过在包含#include 的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:
#include
#define NDEBUG
#include

用法总结与注意事项:
1)在函数开始处检验传入参数的合法性
如:

int resetBufferSize(int nNewSize)
{
    //功能:改变缓冲区大小,
    //参数:nNewSize 缓冲区新长度
    //返回值:缓冲区当前长度
    //说明:保持原信息内容不变 nNewSize<=0表示清除缓冲区
    assert(nNewSize >= 0);
    assert(nNewSize <= MAX_BUFFER_SIZE);

    ...
}

2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败

不好: assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);

好: assert(nOffset >= 0);
    assert(nOffset+nSize <= m_nInfomationSize);


3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题
错误: assert(i++ < 100);
这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
正确: assert(i < 100);
      i++;

4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感

5)有的地方,assert不能代替条件过滤

------------------------------------------------------------

《高质量C/C++编程指南》Page 41-42...

程序一般分为Debug 版本和Release 版本,Debug 版本用于内部调试,Release 版本发行给用户使用。
断言assert 是仅在Debug 版本起作用的宏,它用于检查“不应该”发生的情况。示例6-5 是一个内存复制函数。在运行过程中,如果assert 的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了assert)。

void *memcpy(void *pvTo, const void *pvFrom, size_t size)
{
    assert((pvTo != NULL) && (pvFrom != NULL)); // 使用断言
    byte *pbTo = (byte *) pvTo; // 防止改变pvTo 的地址
    byte *pbFrom = (byte *) pvFrom; // 防止改变pvFrom 的地址
    while(size -- > 0)
        *pbTo ++ = *pbFrom ++ ;
    return pvTo;
}
示例6-5 复制不重叠的内存块

assert 不是一个仓促拼凑起来的宏。为了不在程序的Debug 版本和Release 版本引起差别,assert 不应该产生任何副作用。所以assert 不是函数,而是宏。程序员可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段。如果程序在 assert 处终止了,并不是说含有该assert 的函数有错误,而是调用者出了差错,assert 可以帮助我们找到发生错误的原因。

很少有比跟踪到程序的断言,却不知道该断言的作用更让人沮丧的事了。你化了很多时间,不是为了排除错误,而只是为了弄清楚这个错误到底是什么。有的时候,程序员偶尔还会设计出有错误的断言。所以如果搞不清楚断言检查的是什么,就很难判断错误是出现在程序中,还是出现在断言中。幸运的是这个问题很好解决,只要加上清晰的注释即可。这本是显而易见的事情,可是很少有程序员这样做。这好比一个人在森林里,看到树上钉着一块“危险”的大牌子。但危险到底是什么?树要倒?有废井?有野兽?除非告诉人们“危险”是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略,甚至被删除。

【规则6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
【规则6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性)。
【建议6-5-1】在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
【建议6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。

ASSERT宏中应该包含的元素:

判断条件;输出当前断言失败的位置(文件、行数等);返回错误;终止程序...

几种典型的ASSERT的写法:

VC中的写法:

#define ASSERT(f) /
        do /
        { /
        if (!(f) && AfxAssertFailedLine(THIS_FILE, __LINE__)) /
        AfxDebugBreak(); /
        } while (0) /

#define _ASSERT(expr) /
        do { if (!(expr) && /
            (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, NULL))) /
             _CrtDbgBreak(); } while (0) /

其他平台的写法:

# define ASSERT(x) ((x) || (dbg_printf("assertion failed ("__FILE__":%d): /"%s/"/n",__LINE__,#x), break_point(), FALSE))

 

阅读(1538) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~