宏定义中的#,##,...,do{}while,__VA_ARGS__

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了宏定义中的#,##,...,do{}while,__VA_ARGS__相关的知识,希望对你有一定的参考价值。

宏定义中的#,##

1.在一个预处理器宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组

#define syslog(a) fprintf(stderr,"Warning: " #a"\n");

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

结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义

可变参数的宏定义

在写程序输出的时候,总会遇到一些参数可变的输出,为了方便那么可变参数宏会是一个选择。

在C99中规定宏也可以像函数一样带可变的参数,如:

#define dmsg(format, ...) fprintf(stderr, format, __VA_ARGS__)

其中,...表示参数可变,__VA_ARGS__在预处理中为实际的参数集所替换 

GCC中同时支持如下的形式

#define dmsg(format, args...) fprintf(stderr, format, args)

其用法和上面的基本一致,只是参数符号有变化

有一点需要注意,上述的宏定义不能省略可变参数,尽管你可以传递一个空参数, 这里有必要提到"##"连接符号的用法。

"##"的作用是对token进行连接,在上例中,format、__VA_ARGS__、args即是token,

如果token为空,那么不进行连接,所以允许省略可变参数(__VA_ARGS__和args), 对上述变参宏做如下修改

#define dmsg(format, ...)     fprintf(stderr, format, ##__VA_ARGS__)

或者

#define dmsg(format, args...) fprintf(stderr, format, ##args)

上述的变参宏定义不仅能自定义输出格式,而且配合#ifdef #else #endif在输出管理上也很方便,

比如调试时输出调试信息,正式发布时则不输出,可以这样

#ifdef _ENABLE_DEBUG_

#define dmsg(format, args...) fprintf(stderr, "log: "format"\n", ##args)

#else

#define dmsg(format, args ...)

#endif

例如:

int  main()

{

    dmsg();

    dmsg("%s %s","hello","world");

    dmsg("%s %s %s","hello","linux","world");

    dmsg("%s %s %d","this","count is",123);

    dmsg0;

}

log: hello world

log: hello linux world

log: this count is 123

宏定义中的do...while(0)

最近在考哪代码时经常碰到do...while(0)这种「奇怪」的定义方式,例如:

#define msg(flags, args ...) do { if (msg_test(flags)) {x_msg((flags), args);} EXIT_FATAL(flags); } while (false)

看到之后很疑惑,为什么这样写呢那个?岂不是多次一举

1. 空的宏定义避免warning:

  #define dmsg() do{}while(0)

2.存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现

 使用do{…}while(0)构造后的宏定义,该宏就相当于一个语句块,我们可以在该语句块中做比较复杂的操作,而且不容易影响到宏外面的内容。

3. 保证宏作为一个整体使用

 使用do{…}while(0)构造后的宏定义不会受到大括号、分号等的影响,保证作为一个整体来是实现。(这一点在宏前使用判断语句的情况尤为重要)

  我们看一个例子,在不使用do{…}while(0)时,如下,

有如下宏定义:

#define SKIP_SPACES(p, limit)  \

     { char *lim = (limit);         \

       while (p < lim) {            \

         if (*p++ != ‘ ‘) {         \

           p--; break; }}}

假设有如下一段代码:

if (*p != 0)

   SKIP_SPACES (p, lim);

else ...

一编译,GCC报error: ‘else’ without a previous ‘if’。原来这个看似是一个函数的宏被展开后是一段大括号括起来的代码块,加上分号之后这个if逻辑块就结束了,所以编译器发现这个else没有对应的if。

这个问题一般用do ... while(0)的形式来解决:

#define SKIP_SPACES(p, limit)     \

     do { char *lim = (limit);         \

          while (p < lim) {            \

            if (*p++ != ‘ ‘) {         \

              p--; break; }}}          \

     while (0)

展开后就成了

if (*p != 0)

    do ... while(0);

else ...

这样就消除了分号吞噬问题。

4. 可以在宏中更好的跳转

  这中情况主要出现比较复杂的宏定义中,当我们使用希望在某中情况中跳出宏的执行,do{…}while(0)就提供了很好的方式。我们可以在do{…}while(0)结构的宏定义中使用break语句,跳出宏。

    #define SKIP_SPACES(p, limit)     \

     do { char *lim = (limit);         \

          while (p < lim) {            \

            if (*p++ != ‘ ‘) {         \

              p--; break; }}}          \

     while (0)

本文出自 “Linux_woniu” 博客,请务必保留此出处http://llu1314.blog.51cto.com/5925801/1967062

以上是关于宏定义中的#,##,...,do{}while,__VA_ARGS__的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式宏定义中do...while的意义

do while的作用

do while的作用

do while的作用

C语言陷阱与技巧第7节,define函数式宏定义不能用普通函数代替吗?为什么要使用do{}while包裹代码

代码宏定义中使用循环语句