《C标准库》—之<assert.h>实现

原创 2015年04月25日 01:08:53

首先,贴出标准库中<assert.h>的实现源码:


#undef assert
#ifdef  NDEBUG
#define assert(test)((void)0)
#else
void _Assert(char*);
#define _STR(x) _VAL(x)
#define _VAL(x) #x
#define assert(test)((test)?(void)0:_Assert(__FILE__":"_STR(__LINE)""#test))
#endif



_Assert(char*)的实现如下:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

void _Assert(char * mesg)
{
fputs(mesg, stderr);
fputs(" -- assertion failed\n", stderr);
abort();
}




全部的代码就在这里了。首先,<assert.h> 里有一个 NDEBUG宏,从assert.h的条件宏中可以看出,如果定义了宏NDEBUG, 那么assert测试宏将等于什么都不做,即就是(void)0,那么我们在自己的程序里就可以关闭assert断言或打开它。如果当前assert不存在,那么#undef指令也没任何副作用,因为总是可以#undef一个名字。但是,如果定义有所改变的话,这个#undef指令还是很有必要的。


  如果没有定义NDEBUG, 它将实现它的截断程序的功能,即就是:如果assert(test)里test为真, 证明程序运行到此处没有错误,否则的话,将错误的信息送给_Assert函数,_Assert函数负责将错误信息的源文件(__FILE__)和所在的行(__LINE__) 输出到标准错误流。


我们具体研究下里面的细节。首先是两个宏:__FILE__和__LINE__     __FILE宏会自己扩展为字符串常量,如果错误来自某个.cpp文件或者.c文件,那么它就等同于"***.cpp" 或"***.c",这个宏很好理解,相对于__FILE__,__LINE__的理解不是很容易。__LINE__这个宏并没有扩展为对应行的字符串,而是变成了一个十进制常量,而_Assert函数的参数指向的是个字符串,所以我们有必要把__LINE__转换为相应的字符串。


对于__LINE__的转换,我们使用了C语言中嵌套宏的方法,来实现将十进制常量转换为对应的字符串。

#define _STR(x) _VAL(x)
#define _VAL(x) #x


就是这两个宏的作用。这两个宏缺一不可。#define  _STR(x)   _VAL(x) 这个宏的作用是将__LINE__替换为对应的实参,即就是它的十进制常量,#define _VAL(x)#x 将它的十进制常量变为一个字符串,这个就是宏中#的作用。


那么,具体的是怎么转换的呢,这里有一条关于嵌套宏的展开规则:


遇到宏名后(1)

检查对应的宏体中是否含有#和##运算符
无——处理宏参数(实参)(2)
遇到宏名,回到(1)
没有遇到,在宏体中用实参字符串替换形式参数,再检查是否遇到宏名(4)

有——不检查宏参数,在宏体中用实参字符串替换形式参数,再检查是否含有宏名(3)
遇到宏名,回到(1)
没有遇到,结束 (5)


对应的#define  _STR(__LINE__)  __VAL(__LINE__), #define  __VAL(__LINE__)  #__LINE__,我们就可以这样理解它具体的展开方式了:


首先,检查#define _STR(__LINE__)__VAL(__LINE__)中是否有#或##运算符,这里没有,好,然后在宏的参数表里也没有遇到宏名,那么在这个宏体中,我们就用__LINE__的十进制常量(假设是10)替换了__LINE__,接下来到了 #define  __VAL(__LINE__)   #__LINE__ ,检查过后,发现这个宏体里存在#运算符,那么我们就不检查宏参数了, 直接将实参10替换为"10"  ,这个就是#10的效果。然后我们再检查宏的参数表里有没有遇到宏名,这里没有遇到,结束。

经过这一番转换,我们就可以得到了__LINE__的十进制常量10对应的字符串“10”, 然后连同其它字符串一块传给了_Assert函数。这也是过渡宏_VAL的存在的原因,存在的作用就是把__LINE__替换为10.



我学习过了这个宏后,对宏里一些用法又有了一些了解,比如:


#x 将x变为"x",即添加双引号;

a##b 将a和b黏结起来,ab,不一定是黏结字符串;

#@x  将x变为'x', 即添加了单引号。 



版权声明:需要转载请私信啦~

C标准库<assert.h>实现

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

C /C++标准库 - <cassert> (assert.h)

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

C标准库源码解读(VC9.0版本)——assert.h

先列一下我手头的资料:       1.The C Programming Language       2.The C Standar Library       3. Programmin...

C标准库——assert.h

这个头文件提供两个宏:assert和NDEBUG,assert可以在程序中进行断言,也就是断言为假,输出错误信息并终止运行。NDEBUG说简单点儿算是assert的开关,像这样: #define N...

C标准库源码解剖(9):控制函数assert.h, setjmp.h和signal.h

控制函数用于对C语言程序的标准控制流(如if/else、switch、for等)提供扩展,在头文件assert.h、setjmp.h和signal.h中提供,分别提供表达式断言功能、非本地跳转功能、信...

【转】各种数据类型的取值范围 C/C++标准库中定义的各个类型最大值最小值limit.h

转载自:http://blog.csdn.net/fx677588/article/details/71429366 C/C++标准库中定义的各个类型最大值最小值limit.h           ...

C标准库time.h使用说明

// 作者:Michael Lin // 时间:2014 - 5 -12 近期学习C++标准库时,我发现time.h(ctime)里一些函数经常导致自己困惑或者是误用,因此打算自己写一篇文章总结下...

C 标准库 error.h

对于编程序来讲,我们需要十分注意错误的返回,所以我想要谢谢我对于error.h的理解。           对于早期的unix程序采用的“其他地方”,是一个具有外部链接的数据对象。任何失败的系统调用...

C /C++标准库 - <cerrno> (errno.h)

1. 概述errno中定义了几个宏,其和错误状态的报告有关2. Macroerrno:errno 是记录系统的最后一次错误代码,代码是一个int型的值。 注意:只有当一个库函数失败时,errno才会...

C /C++标准库 - <cstdlib>(stdlib.h)

stdlib.hstandard library标准库头文件,是一个大杂烩,定义和声明一些没有明显的归属的宏和函数。1. Types 序号 Types 描述 1 div_t St...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《C标准库》—之<assert.h>实现
举报原因:
原因补充:

(最多只允许输入30个字)