首先,贴出标准库中<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. 走进C标准库(1)——assert.h,ctype.h

    默默觉得原来的阅读笔记的名字太土了,改了个名字,叫做走进C标准库. 自己就是菜鸟一只,第一次具体看C标准库,文章参杂了对<the standard C library>的阅读和对源码的一些 ...

  2. 彻底弄清c标准库中string.h里的常用函数用法

    在我们平常写的c/c++程序,一些算法题中,我们常常会用到c标准库中string.h文件中的函数,这些函数主要用于处理内存,字符串相关操作,是很有用的工具函数.而且有些时候,在笔试或面试中也会出现让你 ...

  3. 走进C标准库(8)——"string.h"中函数的实现相关字符串操作函数

    我的strcat: char *strcat(char *dest,char *src) { char * reval = dest; while(*dest) dest++; while(*src) ...

  4. 走进C标准库(3)——"stdio.h"中的getc和ungetc

    接前文. 再来看看getc和ungetc的实现.在看这两个函数的实现之前,我们先来想一想这两个函数分别需要做的工作. int getc(FILE *stream) 说明:函数getc从stream指向 ...

  5. 走进C标准库(2)——"stdio.h"中的fopen函数

    其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h. 1.茶余饭后的杂谈,有趣的历史 在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中 ...

  6. C 非标准库(conio.h)

    所谓的 C 标准库(C standard library),是指在 ISO C 或者 POSIX 标准中定义的: POSIX is a superset(超集) of the standard C l ...

  7. C标准头文件<assert.h>

    <assert.h>定义了两个用来调试程序的宏: assert和NDEBUG,assert用来判断表达式是否为真,如果为真继续执行,如果为假,向stderr输出一条错误消息,并调用< ...

  8. 走进C标准库(4)——"stdio.h"中的putc

    花了点时间把园子弄得好看了点,现在继续. 函数名: putc 功  能: 输出一字符到指定流中 用  法: int putc(int ch, FILE *stream); #define _putc_ ...

  9. 走进C标准库(5)——"stdio.h"中的其他部分函数

    函数介绍来自:http://ganquan.info/standard-c/ 函数名: freopen 功  能: 替换一个流 用  法: FILE *freopen(char *filename, ...

  10. 走进C标准库(6)——"string.h"中函数的实现memchr

    我写的memchr: void *memchr(const void *buf, char ch, unsigned count){ unsigned ; while(*(buf++) != ch & ...

随机推荐

  1. PowerMock.expectNew(Class<T> type, Class<?>[] parameterTypes, Object... arguments)

    1:PowerMock.expectNew(Class<T> type, Class<?>[] parameterTypes, Object... arguments) 如果你 ...

  2. visual studio 2013连接Oracle 11g并获取数据:(二:实现)

    1.VS中新建一个winform窗体 (1)一个按钮 (2)一个数据表格视图(在里面显示得到的数据表) 2.双击按钮进入代码 (1)添加 using System.Data.OracleClient; ...

  3. IT公司100题-3-求数组的最大子序列的和

    问题描述: 输入一个整形数组,数组里有正数也有负数. 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和. 求所有子数组的和的最大值.要求时间复杂度为O(n). 例如输入的数组为1, -2 ...

  4. 可以创建专业的客户端/服务器视频会议应用程序的音频和视频控件LEADTOOLS Video Conferencing SDK

    LEADTOOLS Video Streaming Module控件为您创建一个自定义的视频会议应用程序和工具提供所有需要的功能.软件开发人员可以使用Video Streaming Module SD ...

  5. java基础之hashmap

    Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论. 1.hashma ...

  6. 测试V模型

    一:测试V模型 RAD(Rap Application Development 快速引用开发)模型是软件开发过程中的一个重要模型,由于模型构图形似字母V,所以又称软件开发的V模型.他通过开发和测试同时 ...

  7. android 经典下雨效果

    英文原文地址:https://blog.stylingandroid.com/snowfall/ 中文翻译地址:http://www.open-open.com/lib/view/open145226 ...

  8. mysql 1067 启动错误!!!

    图二:服务器启动不成功 -- 解决方法

  9. Ubuntu 14.10 下awk命令详解

    简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再 ...

  10. python随笔

    1. 使用iter实现接收用户多行输入 stopword = '' str = '' print('请将要添加的内容输入下方,输入空白行按回车退出程序:') for line in iter(inpu ...