#, ##, args...以及__VAR_ARGS__宏定义解析

2060阅读 0评论2015-03-29 abin9630
分类:LINUX

在 linux kernel中,经常会看见类似的宏定义

点击(此处)折叠或打开

  1. #define printf(format, args...) \
  2.     printk(KERN_ERR "BFS-fs: %s(): " format, __FUNCTION__, ## args)

更有甚者,在 man printf时候,都会有类似

点击(此处)折叠或打开

  1. int printf(const char *format, ...);
  2.        int fprintf(FILE *stream, const char *format, ...);
  3.        int sprintf(char *str, const char *format, ...);
  4.        int snprintf(char *str, size_t size, const char *format, ...)

初识这些代码,对于其中:
1. " ##" 的含义和作用
2. args...作用
3. ...又是什么,省略号么?
都会有疑问,其作用,含义
同样是 C语言,为何之前学习 C时候,没有相关的介绍有涉及这些内容

对于 #, ##, args...以及 ...都是在预编译时候所使用和识别的,预编译结束后,这些标识都会被一一替换。
这也很好理解,谁让他们都是存在于 #define的代码中的呢

#
这个是用来提取生成字符串的

##
这个是用来连接前后两个(宏)变量的

比如对于

点击(此处)折叠或打开

  1. struct command
  2. {
  3. char *name;
  4. void (*function) (void);
  5. };
  6.  
  7. struct command commands[] =
  8. {
  9. { "quit", quit_command },
  10. { "help", help_command },
  11. ...
  12. }
可以使用如下宏来实现(使用 gcc -E test.c 查看预编译后生成的文件,效果是一样一样的),看起来会更加直观

点击(此处)折叠或打开

  1. #define COMMAND(NAME) { #NAME, NAME ## _command }
  2.  
  3. struct command commands[] =
  4. {
  5. COMMAND (quit),
  6. COMMAND (help),
  7. ...
  8. }
可见
#NAME 将生成 "NAME"的字符串。如果 NAME是宏变量,会在 #起作用前先行展开
NAME ## _command 将会连接前后两个记号(token),生成一个新的记号(token) NAME_command。 当然和上面的类似,如果NAME作为宏变量,在 ##连接前会先行展开

对于 ##,需要补充如下两点:
1. ##连接符生成的新记号(token),应该是一个合法的,有意义的记号。比如你不能连接 "c"和 "-"/ "+"等等类似标记来生成"c-"或者 "c+",否者预编译时候就会提示错误
2. ##连接生成的新记号(虽然已经通过预编译),但是如果生成的是某变量,依旧需要确保该变量名是合法的。比如"c"和 "_-var"虽然可以使用##来进行连接而生成"c_-var"(预编译可以正常完成),但是在后续的编译环节依旧会报错(当然这个是后话,严格来说不属于 ##的预编译讨论范畴)(判断规范,可以参照C语言变量定义)
 3. 所有的注释代码,在 ##进行连接前,预编译器已经将他们翻译成空格符了,因此不能期望使用 ##来连接 /和 *来生成一个注释代码(当然如果不是闲得疼,应该也不会这么用吧?)
4. ##和前后两个记号之间的空格数可以随意;并且因为以上第3点说明,##和记号间是可以插入 /* XXX */类似的注释语句的

点击(此处)折叠或打开

  1. #define MarPrintf(format, ...) printf("==>Debug: %s-%d |" format, __FUNCTION__, __LINE__, ## /* 123 */ __VA_ARGS__)
5. 在最上面的例子的 ##args的使用中,##的作用不是连接 format和 args。而是实现:
" 如果宏扩展时候发现 args不存在时候(也就是只有 format,但是没有 args),"##"配置前面紧邻的 ","(也就是 format后的逗号)来实现删除 format后面的逗号的作用(具体实例说明可以参考下面的描述)

args...
这个是 GNU CPP中对于可变的宏变量所支持的用法,表示后续的 args可能会有多个
个人感觉上,宏进行扩展时候,会以逗号作为标记来进行变量的区分和赋值动作
比如

点击(此处)折叠或打开

  1. #define MarPrintf(format, args...) printf("==>Debug: %s-%d |" format, __FUNCTION__, __LINE__, ## args)

  2.     MarPrintf("Here, sizeof(unVar): %d, %d\n", sizeof(unVar), sizeof(union UnTest))
其中
    "Here, sizeof(unVar): %d, %d\n"赋值给 format。注意连同引号一起都会赋值给 format
    sizeof(unVar), sizeof(union UnTest)会赋值给 args...
当然,对于 format字符串的赋值,可以使用上述的 #来实现,但是有些地方就会需要进行调整

点击(此处)折叠或打开

  1. #define MMarPrintf(format, args...) printf("==>Debug: %s-%d |" #format, __FUNCTION__, __LINE__, ## args)

  2. ...
  3.     MMarPrintf(Here sizeof(unVar): %d\n, sizeof(unVar));
  4.     //MMarPrintf(Here, sizeof(unVar): %d\n, sizeof(unVar))
会被赋值给 format的字串中,因为现在没有引号的作用,其中不能含有逗号,对于上述例子中,如果使用注释的第5句,在编译时候会提示错误

对于后续展开的宏中,是使用 ##args还是使用 args
1. 两者都可以实现可变宏变量的扩展
2. 但是使用 ##args会更有优势
具体原因在上述 ”##“讨论中的第5点中已经有所提及

点击(此处)折叠或打开

  1. #define MarPrintf(format, args...) printf("==>Debug: %s-%d |" format, __FUNCTION__, __LINE__, ## args)
  2. //#define MarPrintf(format, args...) printf("==>Debug: %s-%d |" format, __FUNCTION__, __LINE__, args)

  3.     ...
  4.     MarPrintf("Here, sizeof(unVar)\n" );
  5.     MarPrintf("Here, sizeof(unVar): %d\n", sizeof(unVar));
  6.     MarPrintf("Here, sizeof(unVar): %d, %d\n", sizeof(unVar), sizeof(union UnTest))
如上,因为第5行中没有任何 args变量的存在,而只有 format变量,如果使用第2行的宏定义,在编译时候会提示错误。因为预编译后 第5行展开后将变成

点击(此处)折叠或打开

  1. printf("==>Debug: %s-%d |" "Here, sizeof(unVar)\n", __FUNCTION__, 62, )
而如果使用第1行的宏定义,展开后的语句将是

点击(此处)折叠或打开

  1. printf("==>Debug: %s-%d |" "Here, sizeof(unVar)\n", __FUNCTION__, 62 )
末尾的逗号将会被取消。具体在何种情况下,该逗号会取消,可以参考如下 GNU的 Variadic Macros中的描述

__VAR_ARGS__和 ...
这两者是在 C99的标准中增加的功能,不同于上述的 args...(GNU的CPP中一贯都有支持)。
但是这两个功能是类似的,但是考虑到可移植性(对于不支持 C99的老版本而言),需要进行相应的转换动作,替换成 args...的形式

点击(此处)折叠或打开

  1. #define MarPrintf(format, ...) printf("==>Debug: %s-%d |" format, __FUNCTION__, __LINE__, ## /* 123 */ __VA_ARGS__)
  2. void test_command(void)
  3. {
  4.     MarPrintf("Just for test!\n");
  5. }
具体用法和注意点,和上述的 args..都是类似的,只是格式有所不同而已

###########################参考内容############################


GNU宏定义中的 #args和 ##args

上一篇:结构体特定变量的赋值
下一篇:查看 GCC编译时头文件搜索路径