今天在知乎上看到一个有趣的宏定义:
#define LOG_INFO(fmt, args...) fprintf(stdout, "%d|"fmt"\n", __LINE__, ##args)
一时间竟不知道如何解释这个宏定义 。查阅了相关资料后总算基本了解它的工作原理,让我们一步一步来。
一、基本概念
- 类函数宏
You can also define macros whose use looks like a function call. These are called function-like macros.To define a function-like macro, you use the same ‘#define’ directive, but you put a pair of parentheses immediately after the macro name.
https://gcc.gnu.org并没有一个非常严格的定义, 只要你在宏定义语句
#define [标识符] [替换列表]
中把标识符写成函数的形式,就可以认为是类函数宏。
- #在宏定义中的作用
可以把符号转变成字符串。
#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.
#x分别被替换为“y”和“2+4”,相邻的字符串会自动拼接。
- ##在宏定义中的作用
##把两个语言符号组合成单个语言符号。
#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;
}
输出结果:
x1=12
x##n变成x1变量。
二、解析
#define LOG_INFO(fmt, args...) fprintf(stdout, "%d|"fmt"\n", __LINE__, ##args)
标识符中的...代表可变参数,args表示可变参数的名字,__LINE__是编译器内置的宏定义,表示当前行号。对于以上宏定义,如果我们传入的可变参数为空,会造成fprintf参数中多了一个逗号从而报错,为了解决这个问题,使用##表示如果可变参数为空,预处理器将去除掉它前面的那个逗号。
类似宏定义还有
#define debug(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
这里变参列表...没有名字, _ _VA_ARGS_ _就是可变参数宏,这里相当于把可变参数传到了fprintf里,当然,有的编译器会自动去掉多于逗号,不必使用##,如VS。
三、定义变参日志函数
之前自己写了日志函数链接见这里,但是没有传递变参的功能,又或者自己实现了一套变参传递的功能,当时并不知道变参数宏这个东西。现在重新实现日志函数就方便多了。
#define LOG(fmt,...) _log(fmt,##__VA_ARGS__)
参考:
4.https://gcc.gnu.org/onlinedocs/cpp/Function-like-Macros.html