本文地址:http://www.cnblogs.com/archimedes/p/c-library-assert.html,转载请注明源地址。

1.背景知识

头文件<assert.h>唯一的目的就是提供assert宏定义,可以在程序中关键的地方使用这个宏来进行断言。如果一处断言被证明非真,希望程序在标准错误流输出一条适当的提示信息,并使执行异常终止。

可以这样写代码:

#include<assert.h>
...
assert( <= i && i < sizeof(a) / sizeof(a[]));

当然上面的代码不是实战中的最好的形式,程序异常终止应该改为某种错误的恢复。

宏NDEBUG

可以通过在程序的某些地方定义宏NDEBUG来改变assert的展开方式

如果程序某个包含assert的地方没有定义NDEBUG,该头文件就会将宏assert定义为活动形式,它就可以展开为一个表达式,测试断言并在断言为假的时候输出一条错误信息,然后程序终止。反之,如果定义了NDEBUG,头文件就会把这个宏定义为不执行任何操作的静止形式。

2.<assert.h>的使用

从上面的代码中可以看到,可以使用一个简单的谓词来简化assert:

if(!ok)
abort(); //在头文件<stdlib.h>中声明

如果觉得断言没有存在的必要,就在包含头文件之前加上下面的代码:

#define NDEBUG //取消断言
#include<assert.h>

可以在整个源文件中用不同的方式控制断言,当断言在频繁执行的循环内部发生时,性能可能会急剧下降,或在达到提示性的部分之前,一个更早的断言可能会终止程序。要打开断言,可以写:

#undef NDEBUG
#include<assert.h>

要关闭断言,可以写:

#define NDEBUG
#include<assert.h>

注意:即使宏NDEBUG已经被定义了,我们仍然可以安全地定义它,这是一个良性重定义

3.<assert.h>的实现

从上面的分析知该头文件的大致框架如下:

#undef assert  //消除已定义的
#ifdef NDEBUG
#define assert(expr) ((void) 0) //功能失效
#else
#define assert (expr) ...
#endif

一个简单的编写宏assert的活动形式的方式如下:

#define assert(expr) if(!(expr)) \
fprintf(stderr, "Assertion failed: %s, file %s, line %i\n", \
#expr, __FILE__, __LINE__)

这种方式因为如下几种原因不能接受:

1、宏不能直接调用库的任何输出函数

上面的定义中包含fprintf、stderr等在stdio.h中定义的函数或宏,程序可能没有包含这个头文件

2、宏必须能扩展为一个void类型的表达式

3、宏应该可以扩展为有效并且紧凑的代码

这个版本却总是调用了一个传递了5个参数的函数

修改后的assert宏如下:

#undef assert
#ifdef NDEBUG
#define assert(expr) ((void) 0)
#else
void __bad_assertion (const char *_mess);
#define __str(x) # x
#define __xstr(x) __str(x)
#define assert(expr) ((expr)? (void)0 : \
__bad_assertion("Assertion \"" #expr \
"\" failed, file " __xstr(__FILE__) \
", line " __xstr(__LINE__) "\n"))
#endif

其中__LINE__ 是内置宏,代表该行代码的所在行号,由于__LINE__没有扩展成字符串字面量,它变成了一个十进制常量,把它转换成适当的形式需要一个额外的处理层。向头文件中添加两个隐藏的宏__str和__xstr来实现,其中一个宏用它的十进制常量扩展来取代__LINE__,另一个是把十进制常量转换成一个字符串字面量

宏调用的隐藏库函数__bad_assertion的实现:

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
void __bad_assertion(const char *mess) {
fputs(mess, stderr);
abort();
}

函数__bad_assertion使用了两个其他的库函数,通过调用<stdio.h>中声明的函数fputs把字符串写到标准错误流,并使用abort异常终止程序的执行,有关这些相关头文件以后会详细剖析。

4.<assert.h>的测试

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
int main( void )
{
FILE *fp;
fp = fopen( "test.txt", "w" );//以可写的方式打开一个文件,如果不存在就创建一个同名文件
assert( fp ); //所以这里不会出错
fclose( fp ); fp = fopen( "noexitfile.txt", "r" );//以只读的方式打开一个文件,如果不存在就打开文件失败
assert( fp ); //所以这里出错
fclose( fp ); //程序永远都执行不到这里来
return ;
}

注意:

1.在函数开始处检验传入参数的合法性如:

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

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

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

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

3.不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题,如:

错误:

assert(i++ < );

这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。

正确:

assert(i < );
i++;

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

5.在有的地方,assert不能代替条件过滤。

参考资料

《C标准库》

C标准库<assert.h>实现的更多相关文章

  1. C 标准库 - ctype.h

    C 标准库 - ctype.h This header declares a set of functions to classify and transform individual charact ...

  2. C标准库<ctype.h>实现

    本文地址:http://www.cnblogs.com/archimedes/p/c-library-ctype.html,转载请注明源地址. 1.背景知识 ctype.h是C标准函数库中的头文件,定 ...

  3. C 标准库 - ctype.h之iscntrl 使用

    iscntrl int iscntrl ( int c ); Check if character is a control character 检查给定字符是否为控制字符,即编码 0x00-0x1F ...

  4. C 标准库 - ctype.h之isalpha使用

    isalpha int isalpha ( int c ); Checks whether c is an alphabetic letter. 检查给定字符是否字母字符,即是大写字母( ABCDEF ...

  5. C 标准库 - ctype.h之isalnum使用

    isalnum int isalnum ( int c ); Checks whether c is either a decimal digit or an uppercase or lowerca ...

  6. C 标准库 - string.h

    C 标准库 - string.h This header file defines several functions to manipulate C strings and arrays. stri ...

  7. C 标准库 - <assert.h>

    C 标准库 - <assert.h> 简介 C 标准库的 assert.h头文件提供了一个名为 assert 的宏,它可用于验证程序做出的假设,并在假设为假时输出诊断消息. 已定义的宏 a ...

  8. C 标准库 - <stdarg.h>

    C 标准库 - <stdarg.h> 简介 stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数. 可变参 ...

  9. C 标准库 - <signal.h>

    C 标准库 - <signal.h> 简介 signal.h 头文件定义了一个变量类型 sig_atomic_t.两个函数调用和一些宏来处理程序执行期间报告的不同信号. 库变量 下面是头文 ...

  10. C 标准库 - <setjmp.h>

    C 标准库 - <setjmp.h> 简介 setjmp.h 头文件定义了宏 setjmp().函数 longjmp() 和变量类型 jmp_buf,该变量类型会绕过正常的函数调用和返回规 ...

随机推荐

  1. Git 文件比较

    Git 的三个作业场: 工作区(Work Tree) 项目根目录下 .git 目录以外所有区域,是编辑项目文件的地方. 缓存区(Index) 工作区文件必须先保存在缓存区,之后从缓存区保存到仓库. 仓 ...

  2. JavaScript面试库

    1.将一段字符串转成驼峰命名法. var str = "web-kit-index"; function to(str){ var j = str.split("-&qu ...

  3. Awk by Example--转载

    原文地址: http://www.funtoo.org/Awk_by_Example,_Part_1?ref=dzone http://www.funtoo.org/Awk_by_Example,_P ...

  4. jquery选中下拉列表的某个值

    $('#villageToiletAnn').val('xxx'); id是select的ID,不是option的ID

  5. MySQL体系结构以及各种文件类型学习

    1,mysql体系结构 由数据库和数据库实例组成,是单进场多线程架构. 数据库:物理操作系统文件或者其它文件的集合,在mysql中,数据库文件可以是frm.myd.myi.ibd等结尾的文件,当使用n ...

  6. Spring基础——在 Spring Config 文件中基于 XML 的 Bean 的自动装配

    一.Spring IOC 容器支持自动装配 Bean,所谓自动装配是指,不需要通过 <property> 或 <constructor-arg> 为 Bean 的属性注入值的过 ...

  7. ok6410 android driver(9)

    In this essay, I will write the JNI to test our leds device. If you don't know how to create a jni p ...

  8. thread_fork/join并发框架1

    一.线程并发控制  Thread.Executor.ForkJoin和Actor  1.裸线程      Runnable接口,调用.start()开始,没有现成的API来结束线程,你需要自己来实现, ...

  9. C#设计模式——观察者模式(Observer Pattern)

    一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...

  10. ASP.NET中使用DropDownList实现无刷新二级联动详细过程

    Demo.sql create table Car( [id] int identity, ) not null, ) not null ) go insert into Car ([brand],[ ...