今天在知乎上看到一个有趣的宏定义: 

#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