《C标准库》—之<assert.h>实现
2015-04-25 01:08
197 查看
首先,贴出标准库中<assert.h>的实现源码:
_Assert(char*)的实现如下:
全部的代码就在这里了。首先,<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) 这个宏的作用是将__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', 即添加了单引号。
#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', 即添加了单引号。
相关文章推荐
- java实现万年历<51cto学院学习笔记>
- 《C++第九周实验报告2-1》---接第8周任务2,定义Time类中的<<和>>运算符重载,实现时间的输入输出
- <仅是自己做笔记。。。系列15>实现一个挺高级的字符匹配算法: 给一串很长字符串,要求找到符合要求的字符串,例如目的串:123 1******3***2 ,12*****3这些都要找出来
- spring mvc 实现任意文件上传—— 下载<二>
- 数组问题之一维最大字段和问题<Java实现>
- <转>介绍Robotium+Orange实现androidUI自动化测试
- <vector> template实现
- Spring零配置通过注解实现Bean依赖注入总结<转>
- <Win32 API> 钩子程序的实现
- Android UI设计之<五>自定义DrawView组件,实现数字签名效果
- HTML <area> 标签实现带有可点击区域的图像映射
- Object-C中对自定义类实现<NSCopying>协议
- <s:radio>标签和<s:checkboxlist>标签实现换行
- <二叉树 前中后 层序 非递归遍历 c语言实现>
- 《C标准库》——之<stdarg.h>
- 分类算法--贝叶斯分类法(Maprdecue实现)<转>
- C标准库学习之<assert.h> ——诊断
- Android实战简易教程<二十九>基于Face++实现年龄识别APP(一))
- 针对android&ios yuv旋转、镜像、格式转换、裁剪 算法实现<转>
- Comparable<T>接口实现