可变宏
Posted 悄然拔尖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了可变宏相关的知识,希望对你有一定的参考价值。
C99中规定宏可以像函数一样带有可变参数,实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个英文输入法下的句号)。这样预定义宏__VA_ARGS__就可以被用在替换部分中,以表明省略号代表什么。
eg:
#include<stdio.h>
#define Variable_Macro(...) printf(__VA_ARGS__)
int main(void)
{
Variable_Macro("This is a variable macro test...\\n");
Variable_Macro("My age is %d",24);
return 0;
}
这里和可变参数函数不同,可变宏可以只有可变参数“...”,但是可变参数函数必须在可变参数前有至少一个其他变量,否则会报错:
但是可变宏也得遵守约定——可变参数只能出现在宏的最后,不能在“...”后面再加参数:
#define Variable_Macro(...,a) 这样是错误的。
利用宏参数创建字符串:#运算符
eg:
#include<stdio.h> #define Parameter_Macro(y) printf("The square of y is %d\\n",((y)*(y))) int main(void) { Parameter_Macro(10); return 0; }
引号中的字符串y被当做了普通文本,而不是被看做一个可以替换的语言符号,此时我们可以通过#运算符达到目的。
#include<stdio.h> #define Parameter_Macro(y) printf("The square of " #y " is %d\\n",((y)*(y))) int main(void) { Parameter_Macro(10); return 0; }
顺便多说一句,ANSI C字符串本就具有拼接功能,在宏中使用 #y 时,可以把实参的字符串传进来。
字符串拼接test:
int main(void) { printf("hello" "boy\\n"); return 0; }
就算两个双引号之外隔了很多空格,拼接之后也是紧挨着的,要想展现空格,请让空格位于双引号内部。
预处理器的粘合剂:##运算符
和#运算符一样,##运算符可以用于类函数的替换部分,另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。
eg:
#define XNAME(n) x##n int main(void) { int XNAME(1)=100;//等价于 int x1=100 printf("XNAME(1) is %d",XNAME(1)); return 0; }
##运算符把上例的x和n组合成了一个字符。
如果你觉得你已经掌握了#和##在宏中用法,那么看看下面的呢?
eg:
#include<stdio.h> #define Print_ERROR_1(fmt,...) printf("<<-Print-ERROR_1->> "fmt"\\n",__VA_ARGS__) #define Print_ERROR_2(fmt,arg...) printf("<<-Print-ERROR_2->> "fmt"\\n",arg) int main(void) { Print_ERROR_1("%d",1); Print_ERROR_2("%s","do you konw?"); return 0; }
第一个不难理解,和前面介绍的可变参数宏一样,用__VA_ARGS__作为替换。可是第二个看起来似乎有点不一样,在可变参数三个句号前还加了一个arg,居然这样也行?
是的,它们都是可行的,而且效果都是一样的。但是,我们把main函数中的调用改变一下呢,看看会发生什么:
#include<stdio.h> #define Print_ERROR_1(fmt,...) printf("<<-Print-ERROR_1->> "fmt"\\n",__VA_ARGS__) #define Print_ERROR_2(fmt,arg...) printf("<<-Print-ERROR_2->> "fmt"\\n",arg) int main(void) { Print_ERROR_1("why ?"); Print_ERROR_2("occured err?"); return 0; }
是的,我们只改变了调用形式,当可变参数为0时,发生了错误:
那么你知道这是为什么吗?
我们看看预编译之后的程序:
注意,在没有使用可变参数的时候,最后宏替换在printf函数末尾出现了逗号,这是语法错误,必然导致调用出错。
我们测试最熟悉的printf函数,用错误的方式调用:
报错和上面宏展开一样,知道这里在可变参数为0个的时候会出错,那么怎么解决这个问题呢?
eg:
#include<stdio.h> #define Print_ERROR_1(fmt,...) printf("<<-Print-ERROR_1->> "fmt"\\n",##__VA_ARGS__) #define Print_ERROR_2(fmt,arg...) printf("<<-Print-ERROR_2->> "fmt"\\n",##arg) int main(void) { Print_ERROR_1("why ?"); Print_ERROR_2("right now?"); return 0; }
是的,我们所做的更改很少,仅仅增加了上面红色部分,增加了##运算符,它就可行了:
看看预编译之后的程序:
是的,末尾没有逗号了,成功也是必然的。
那么,我们再测试一下这样的宏能在有可变参数的情况下运行吗?
#include<stdio.h> #define Print_ERROR_1(fmt,...) printf("<<-Print-ERROR_1->> "fmt"\\n",##__VA_ARGS__) #define Print_ERROR_2(fmt,arg...) printf("<<-Print-ERROR_2->> "fmt"\\n",##arg) int main(void) { Print_ERROR_1("%s","yes"); Print_ERROR_2("%s","I can"); return 0; }
看来后者应该是我们程序中应该出现的,它可以在可变参数有或者没有的时候都能正常工作,这样的宏,一般用作打印调试信息,例如STM32 IIC,SPI中都有这样的宏:
那么,问题来了,为什么加了##运算符就成功了,逗号是怎么消失的?
这里,请翻上去看看我举的第一个##运算符的例子,他可以作为预处理器的粘合剂,可以把##前面和后面的字符链接成一个。
以#define Print_ERROR_2(fmt,arg...) printf("<<-Print-ERROR_2->> "fmt"\\n",##arg)为例子
最后那里:,##arg 在没可变参数的时候,##把逗号给吸收了。在没有加上##时,调用Print_ERROR_2(fmt) ,会被展开成
printf("<<-Print-ERROR_2->> "fmt"\\n",) 最后的逗号的存在会让程序出现语法错误,为了消除这个逗号,我们使用##运算符,在有##时,调用展开成:printf("<<-Print-ERROR_2->> "fmt"\\n") 为什么最后的逗号不见了,因为它被预处理器粘合剂##吸收了。让逗号跟着后面的可变参数,如果有一个可变参数,例如a,这样上面展开成printf("<<-Print-ERROR_2->> "fmt"\\n",a) ,如果没有可变参数,逗号和空结合,逗号会被预处理器特殊处理,达到让逗号消失的作用。如果可变参数被忽略或为空,\'##\'操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,它也会正常工作,它会把这些可变参数放到逗号的后面。当##遇到后面是可变参数的时候,编译器的预处理器会特殊处理,例如:#define XNAME(n) ,##n这样的宏编译会出错,但是改成#define XNAME(n...) ,##n之后编译就会通过,这是预处理做了特殊处理,我们并不知道编译器设计者如何去实现的,但是我们应该知道这个特殊的应用场景。
以上是关于可变宏的主要内容,如果未能解决你的问题,请参考以下文章
带有可变参数的嵌套宏在 GCC 中编译,但在 MSVC 中不编译
在 GCC 中将 0 参数传递给可变参数宏失败,但仅在 C++ 中?