• 宏定义中的#,##操作符和... and _ _VA_ARGS_ _与自定义调试信息的输出


    自定义调试信息的输出
      调试信息的输出方法有很多种, 例如直接用printf, 或者出错时使用perror, fprintf等将信息直接打印到终端上, 在Qt上面一般使用qDebug,而守护进程则一般是使用syslog将调试信息输出到日志文件中等等...
      使用标准的方法打印调试信息有时候不是很方便, 例如Qt编程, 在调试已有的代码时, 我想在打印调试信息的地方, 把代码位置也打印出来以方便定位错误, 或者需要在调试信息前面加一个前辍, 好方便在调试信息太多的时候可以用grep过滤一下, 仅显示本模块的调试信息, 这时就需要一个一个地修改已有的qDebug, 使其成为以下形式:
      qDebug( "[模块名称] 调试信息 File:%s, Line:%d", __FILE__, __LINE__ );
      这样的修改比较烦人, 而且一不小心会遗漏某个没改的...
      为了能方便地管理调试信息的输出,一个比较简单的方法就是自已定义一个打印调试信息的宏, 然后替换原来的,废话就不多说了,直接给出一个现成的,下面是一个例子, 我用WiFi表示当前代码的模块名称,我要求在模块中的所有调试信息前面均带有[WiFi]前辍,这样我就能方便地只需使用命令行 | grep "\[WiFi\]"来过滤掉来自其它模块的调试信息了:
    #define qWiFiDebug(format, ...) qDebug("[WiFi] "format" File:%s, Line:%d, Function:%s", ##__VA_ARGS__, __FILE__, __LINE__ , __FUNCTION__);
      上面的宏是使用qDebug输出调试信息,在非Qt的程序中也可以改为printf,守护进程则可以改为syslog等等... 其中,决窍其实就是这几个宏 ##__VA_ARGS__, __FILE__, __LINE__ 和__FUNCTION__,下面介绍一下这几个宏:
      1) __VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错, 你可以试试。
      2) __FILE__ 宏在预编译时会替换成当前的源文件名
      3) __LINE__宏在预编译时会替换成当前的行号
      4) __FUNCTION__宏在预编译时会替换成当前的函数名称
      有了以上这几个宏,特别是有了__VA_ARGS__ ,调试信息的输出就变得灵活多了。


      有时,我们想把调试信息输出到屏幕上,而有时则又想把它输出到一个文件中,可参考下面的例子:
    //debug.c
    #include <stdio.h>
    #include <string.h>
    //开启下面的宏表示程序运行在调试版本, 否则为发行版本, 这里假设只有调试版本才输出调试信息
    #define _DEBUG
    #ifdef _DEBUG
        //开启下面的宏就把调试信息输出到文件,注释即输出到终端
        #define DEBUG_TO_FILE
        #ifdef DEBUG_TO_FILE
            //调试信息输出到以下文件
            #define DEBUG_FILE "/tmp/debugmsg"
            //调试信息的缓冲长度
            #define DEBUG_BUFFER_MAX 4096
            //将调试信息输出到文件中
            #define printDebugMsg(moduleName, format, ...) {\
                char buffer[DEBUG_BUFFER_MAX+1]={0};\
                snprintf( buffer, DEBUG_BUFFER_MAX \
                        , "[%s] "format" File:%s, Line:%d\n", moduleName, ##__VA_ARGS__, __FILE__, __LINE__ );\
           &nbsp;     FILE* fd = fopen(DEBUG_FILE, "a");\
                if ( fd != NULL ) {\
                    fwrite( buffer, strlen(buffer), 1, fd );\
                    fflush( fd );\
                    fclose( fd );\
                }\
            }
        #else
            //将调试信息输出到终端
            #define printDebugMsg(moduleName, format, ...) \
                      printf( "[%s] "format" File:%s, Line:%d\n", moduleName, ##__VA_ARGS__, __FILE__, __LINE__ );
        #endif //end for #ifdef DEBUG_TO_FILE
    #else
        //发行版本,什么也不做
        #define printDebugMsg(moduleName, format, ...)
    #endif //end for #ifdef _DEBUG
    int main(int argc, char** argv)
    {
        int data = 999;
        printDebugMsg( "TestProgram", "data = %d", data );
        return 0;
    }

    =========================================================================

    1.#
    假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化(stringizing).
    #incldue <stdio.h>
    #define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))
    int main(void)
    {
        int y =4;
        PSQR(y);
        PSQR(2+4);
        return 0;
    }
    输出结果:
    the square of y is 16.
    the square of 2+4 is 36.
    第一次调用宏时使用“y”代替#x;第二次调用时用“2+4"代#x。
    2.##
    ##运算符可以使用类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:
    #define XNAME(n) x##n
    这样宏调用:
    XNAME(4)
    展开后:
    x4
    程序:
    #include <stdio.h>
    #define XNAME(n) x##n
    #define PXN(n) printf("x"#n" = %d\n",x##n)
    int main(void)
    {
        int XNAME(1)=12;//int x1=12;
        PXN(1);//printf("x1 = %d\n", x1);
        return 0;
    }
    3.可变宏 ...和_ _VA_ARGS_ _
    实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,以表示省略号代表什么。比如:
    #define PR(...) printf(_ _VA_ARGS_ _)
    PR("hello");-->printf("hello");
    PR("weight = %d, shipping = $.2f",wt,sp);
        -->printf("weight = %d, shipping = $.2f",wt,sp);
    省略号只能代替最后面的宏参数。
    #define W(x,...,y)错误!

     -------------------------------------------------------------------------------------------------------------------------------------

    关于记号粘贴操作符(token paste operator): ##
    1. 简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。
       其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格
       解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,
       被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些
       ##来替代空格。
       另外一些分隔标志是,包括操作符,比如 +, -, *, /, [,], …,所以尽管下面的
       宏定义没有空格,但是依然表达有意义的定义: define add(a, b)  a+b
       而其强制连接的作用是,去掉和前面的字符串之间的空格,而把两者连接起来。
    2. 举列 – 试比较下述几个宏定义的区别
       #define A1(name, type)  type name_##type##_type 或
       #define A2(name, type)  type name##_##type##_type
       A1(a1, int);  /* 等价于: int name_int_type; */
       A2(a1, int);  /* 等价于: int a1_int_type;   */
       解释:
            1) 在第一个宏定义中,”name”和第一个”_”之间,以及第2个”_”和第二个
       ”type”之间没有被分隔,所以预处理器会把name_##type##_type解释成3段:
       “name_”、“type”、以及“_type”,这中间只有“type”是在宏前面出现过
        的,所以它可以被宏替换。
            2) 而在第二个宏定义中,“name”和第一个“_”之间也被分隔了,所以
       预处理器会把name##_##type##_type解释成4段:“name”、“_”、“type”
       以及“_type”,这其间,就有两个可以被宏替换了。
            3) A1和A2的定义也可以如下:
               #define A1(name, type)  type name_  ##type ##_type  
                                          <##前面随意加上一些空格>
               #define A2(name, type)  type name ##_ ##type ##_type
        结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义
    3. 其他相关 – 单独的一个 #
       至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如
          #define  __stringify_1(x)   #x
    那么
          __stringify_1(linux)   <==>  ”linux”
    所以,对于MODULE_DEVICE_TABLE
         1) #define MODULE_DEVICE_TABLE(type,name)                        
                 MODULE_GENERIC_TABLE(type##_device,name)
         2) #define MODULE_GENERIC_TABLE(gtype,name)                      
                 extern const struct gtype##_id __mod_##gtype##_table     
                 __attribute__ ((unused, alias(__stringify(name))))
    得到  
          MODULE_DEVICE_TABLE(usb, products)  
                                 /*notes: struct usb_device_id products; */
     <==> MODULE_GENERIC_TABLE(usb_device,products)
     <==> extern const struct usb_device_id __mod_usb_device_table     
                 __attribute__ ((unused, alias(”products”)))   
    注意到alias attribute需要一个双引号,所以在这里使用了__stringify(name)来
    给name加上双引号。另外,还注意到一个外部变量”__mod_usb_device_table”被alias
    到了本驱动专用的由用户自定义的变量products<usb_device_id类型>。这个外部变量
    是如何使用的,更多的信息请参看《probe()过程分析》。
  • 相关阅读:
    Junit单元测试
    Stream流方法引用
    Stream流思想和常用方法
    算法
    函数式接口
    Zookeeper理解
    GreenPlum学习之(Share-nothing)架构
    链表反转问题
    KMP算法的java实现
    KMP详解之二
  • 原文地址:https://www.cnblogs.com/dongzhiquan/p/1998260.html
Copyright © 2020-2023  润新知