在阅读Linux内核代码时,经常会看到如下的宏定义,
它们叫做可变参数宏,下面我们就来聊聊可变参数宏的用法。
首先点一下带参宏定义的一般形式为:
#define 宏名(形参表) 字符串
带参数的宏调用一般形式为:
宏名(实参表)
1. #的使用
在C语言的宏中,#的功能就是将字符串中的宏参数进行字符串转化操作,简单说就是在将字符串中的宏变量原样输出并在其左右各加上一个双引号。比如下面代码中的宏:
#define WARN_IF(EXP) do{ if(EXP) fprintf(stderr, "Warning: " #EXP "\n"); }while(0)
那么实际使用中会出现下面所示的替换过程:
WARN_IF (d == 0);
被替换为do {
if (d == 0)
fprintf(stderr, "Warning" "d == 0" "\n");
} while(0);
这样每次d为0的时候便会在标准错误流上输出一个提示信息。
2 ##的使用
##被称为连接符,用来将两个字符串连接为一个字符串。比如下面代码中:
struct command
{
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME ## _command }
然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:
struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}
COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。当然,我们还可以n个##符号连接 n+1个字符串,这个特性也是#符号所不具备的。比如:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 这里这个语句将展开为:
typedef struct _record_type name_company_position_salary;
...在C宏中称为变参宏。比如文章一开始提到的:
#define dev_info(dev, fmt, arg...) _dev_info(dev, fmt, ##arg)
或者
#define dev_emerg_once(dev, fmt, ...) \
dev_level_once(dev_emerg, dev, fmt, ##__VA_ARGS__)
第一个宏中,显式地命名变参为arg,那么在宏定义中就可以用arg来代指变参了。
第二个宏中,由于没有对变参起名,用默认的宏__VA_ARGS__来替代它。
这里,再举一个简单的例子:
#define pr_debug(fmt,arg...) printf(err,fmt,arg)
当arg有一个或多个参数时
pr_debug("Tom","hello");
替换为 printf("OK","Tom","hello")
这显然时没有错误的。但是当arg有没有参数时,
printf("OK","Tom",);
这是一个语法错误,不能正常编译。这时,##这个连接符号就可以起到关键作用了。
#define pr_debug(fmt,arg...) printf(err,fmt,##arg)
当 arg 不代表任何参数时,使用“##”就能使前面的逗号变得多余了(丢弃前面的逗号)。
就是说
pr_debug("Tom");
应该替换为
printf("OK","Tom");
而不是
printf("OK","Tom",);