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


    首先,贴出标准库中<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
    ", 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', 即添加了单引号。 



  • 相关阅读:
    springboot启动加载自定义插件
    sql记录
    集成WebMvcConfigurationSupport后,swagger3.0页面404无法访问 heamin
    [HTML/CSS] 超炫Loading动画
    [HTML/CSS] 简单的Menu图标
    Mysql笔记
    性能指标
    Select IO多路复用。
    Java垃圾回收
    JAVAAQS(AbstractQueuedSynchronizer)
  • 原文地址:https://www.cnblogs.com/averson/p/5096084.html
Copyright © 2020-2023  润新知