c语言里的宏(翻译)7
原文在这里
可变参数宏
宏可以接收可变参数列表,就跟函数一样。定义可变参数的语法和函数也差不多。这里有一个例子:
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
这类宏被称为可变参数宏。当一个宏被调用时,第一个命名参数之后的所有符号,包括逗号在内,都变成参数列表。这个符号序列在宏展开时会替换宏内容里的__VA_ARGS__ 符号。因此,我们有如下展开:
eprintf ("%s:%d: ", input_file, lineno) ==> fprintf (stderr, "%s:%d: ", input_file, lineno)
可变参数在插入到宏展开之前先被展开,如同普通参数一样。你可以使用 "#" 和 "##" 操作符 对可变参数进行字符化和粘贴(但是,"##"需要注意一个重要的特殊例子,如下面所讲)
如果你的宏很复杂,你也许需要一个比__VA_ARGS__更具描述性的参数名。CPP允许这类东西作为扩展存在。你可以在...之前写一个参数名,这个名字将被当成可变参数。所以,eprintf 宏可以写成如下形式:
#define eprintf(args...) fprintf (stderr, args)
这种情况下你不能再使用__VA_ARGS__。
在一个可变参数宏里,你可以同时使用命名参数和可变参数。我们可以把eprintf定义成这样:
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)
该公式看上去更加直观,不幸的是它不够灵活:你必须在格式化字符串之后跟至少一个参数。在标准C里,用于区分命名参数和可变参数的逗号不能被省略。如果你的可变参数列表为空,那么你将得到一个语法错误,因为在格式化字符串之后留了一个多余的逗号。
eprintf("success!\n", ); ==> fprintf(stderr, "success!\n", );
GNU CPP 有两个扩展用于处理此类事件。第一,你可以留可变参数为空:
eprintf ("success!\n") ==> fprintf(stderr, "success!\n", );
第二,当用于粘贴逗号和可变参数的`##'符号有特殊含义。如果你写这样的语句
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
而且如果使用eprintf时可变参数列表为空,那么多余的逗号会被字段删除。如果你把全部参数留空,或者在此种情形之外的地方使用`##',那么多余的逗号不会被删除。
eprintf ("success!\n") ==> fprintf(stderr, "success!\n");
上面这个表达式有一个地方是模棱两可的:唯一的参数"success!\n"是可变参数还是命名参数。区分空参数和参数缺失是毫无意义的。C99标准明确规定逗号必须被保留,但是现有的GCC扩展没有这样做。所以CPP在必须遵守C99的情况下保留逗号,其他情况下去除。
C99要求__VA_ARGS__可以出现的唯一情况是在可变参数宏的替换列表里。它不可以被用作宏名,宏参数名,或者在两个不同类型的宏里。它也有可能是不允许在打开文件中被使用的,这此方面,c标准没有定义的很清楚。我们建议,除非必要,不使用__VA_ARGS__。
可变参数宏是C99里的新特性。GNU CPP已经支持这种特性很长时间了,但是只在跟一个命名参数的情况下(类似args...', 而不是 `...' 或 __VA_ARGS__
)。如果你对跟老版本GCC的兼容性很关心,那么你就得使用命名参数列表。另一方面,如果你很关心要跟其他C99标准的程序保持兼容,那么除了__VA_ARGS__其他什么都别用。
以前版本的CPP更广泛的使用逗号删除特性。当前版本里此特性已经被限制了,因为要尽可能的符合C99标准。为了同时满足C99和以前的GCC,在`##'之前的符号必须是逗号,而且逗号之前必须有空格:
#define eprintf(format, args...) fprintf (stderr, format , ##args)