1.Preprocessor Glue: The ## Operatorlinux
预处理链接符:##操做符c++
Like the # operator, the ## operator can be used in the replacement section of a function-like macro.Additionally, it can be used in the replacement section of an object-like macro. The ## operator combines two tokens into a single token. 算法
##将两个符号链接成一个。数组
For example, you could do this:app
#define XNAME(n) x ## nide
Then the macro函数
XNAME(4)优化
would expand to the following:ui
x4this
Listing 1 uses this and another macro using ## to do a bit of token gluing.
// glue.c -- use the ## operator
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void)
{
int XNAME(1) = 14; // becomes int x1 = 14;
int XNAME(2) = 20; // becomes int x2 = 20;
PRINT_XN(1); // becomes printf("x1 = %d\n", x1);
PRINT_XN(2); // becomes printf("x2 = %d\n", x2);
return 0;
}
Here's the output:
x1 = 14
x2 = 20
Note how the PRINT_XN() macro uses the # operator to combine strings and the ## operator to combine tokens into a new identifier.
2.Variadic Macros: ... and _ _VA_ARGS_ _
Some functions, such as printf(), accept a variable number of arguments. The stdvar.h header file,provides tools for creating user-defined functions with a variable number of arguments. And C99 does the same thing for macros.Although not used in the standard, the word variadic has come into currency to label this facility. (However, the process that has added stringizing and variadic to the C vocabulary has not yet led to labeling functions or macros with a fixed number of arguments as fixadic functions and normadic macros.)
The idea is that the final argument in an argument list for a macro definition can be ellipses (that is, three periods)(省略号). If so, the predefined macro _ _VA_ARGS_ _ can be used in the substitution part to indicate what will be substituted for the ellipses. For example, consider this definition:
#define PR(...) printf(_ _VA_ARGS_ _)
Suppose you later invoke the macro like this:
PR("Howdy");
PR("weight = %d, shipping = $%.2f\n", wt, sp);
For the first invocation, _ _VA_ARGS_ _ expands to one argument:
"Howdy"
For the second invocation, it expands to three arguments:
"weight = %d, shipping = $%.2f\n", wt, sp
Thus, the resulting code is this:
printf("Howdy");
printf("weight = %d, shipping = $%.2f\n", wt, sp);
Listing 2 shows a slightly more ambitious example that uses string concatenation and the # operator:
// variadic.c -- variadic macros
#include <stdio.h>
#include <math.h>
#define PR(X, ...) printf("Message" #X ": " _ _VA_ARGS_ _)
int main(void)
{
double x = 48;
double y;
y = sqrt(x);
PR(1, "x = %g\n", x);
PR(2, "x = %.2f, y = %.4f\n", x, y);
return 0;
}
In the first macro call, X has the value 1, so #X becomes "1". That makes the expansion look like this:
(#为参数加双引号。)
print("Message " "1" ": " "x = %g\n", x);
Then the four strings are concatenated, reducing the call to this:
print("Message 1: x = %g\n", x);
Here's the output:
Message 1: x = 48
Message 2: x = 48.00, y = 6.9282
Don't forget, the ellipses have to be the last macro argument:
#define WRONG(X, ..., Y) #X #_ _VA_ARGS_ _ #y(这个是错误的例子。)
上面的宏是使用qDebug输出调试信息,在非Qt的程序中也能够改成printf,守护进程则能够改成syslog等等... 其中,决窍其实就是这几个宏 ##__VA_ARGS__, __FILE__, __LINE__ 和__FUNCTION__,下面介绍一下这几个宏:
1) __VA_ARGS__ 是一个可变参数的宏,不多人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前彷佛只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的做用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的做用,不然会编译出错, 你能够试试。
2) __FILE__ 宏在预编译时会替换成当前的源文件名
3) __LINE__宏在预编译时会替换成当前的行号
4) __FUNCTION__宏在预编译时会替换成当前的函数名称
1.#
假如但愿在字符串中包含宏参数,ANSI C容许这样做,在类函数宏的替换部分,#符号用做一个预处理运算符,它能够把语言符号转化程字符串。例如,若是x是一个宏参量,那么#x能够把参数名转化成相应的字符串。该过程称为字符串化(stringizing).
#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.
第一次调用宏时使用“y”代替#x;第二次调用时用“2+4"代#x。
2.##
##运算符能够用于类函数宏的替换部分。另外,##还能够用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:
#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
3.可变参数宏 ...和_ _VA_ARGS_ _
__VA_ARGS__ 是一个可变参数的宏,不多人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前彷佛只有gcc支持(VC6.0的编译器不支持)。
实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预约义宏_ _VA_ARGS_ _就能够被用在替换部分中,替换省略号所表明的字符串。好比:
#define PR(...) printf(__VA_ARGS__)
int main()
{
int wt=1,sp=2;
PR("hello\n");
PR("weight = %d, shipping = %d",wt,sp);
return 0;
}
输出结果:
hello
weight = 1, shipping = 2
省略号只能代替最后面的宏参数。
#define W(x,...,y)错误!
较大的项目都会用大量的宏定义来组织代码,你能够看看/usr/include下面的头文件中用 了多少个宏定义。看起来宏展开就是作个替换而已,其实里面有比较复杂的规则,C语言有不少复杂但不经常使用的语法规则本书并不涉及,但有关宏展开的语法规则本 节却力图作全面讲解,由于它很重要也很经常使用。
2.1. 函数式宏定义
之前咱们用过的#define N 20或#define STR "hello, world"这种宏定义能够称为变量式宏定义(Object-like Macro),宏定义名能够像变量同样在代码中使用。另一种宏定义能够像函数调用同样在代码中使用,称为函数式宏定义(Function-like Macro)。例如编辑一个文件main.c:
#define MAX(a, b) ((a)>(b)?(a):(b))
k = MAX(i&0x0f, j&0x0f)
咱们想看第二行的表达式展开成什么样,能够用gcc的-E选项或cpp命令,尽管这个C程序不合语法,但不要紧,咱们只作预处理而不编译,不会检查程序是否符合C语法。
$ cpp main.c
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"
k = ((i&0x0f)>(j&0x0f)?(i&0x0f):(j&0x0f))
就像函数调用同样,把两个实参分别替换到宏定义中形参a和b的位置。注意这种函数式宏定义和真正的函数调用有什么不一样:
一、函数式宏定义的参数没有类型,预处理器只负责作形式上的替换,而不作参数类型检查,因此传参时要格外当心。
二、调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不一样。若是MAX是个真正的函数,那么它的函数体return a > b ? a : b;要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而若是MAX是个函数式宏定义,这个宏定义自己倒没必要编译生成指令,可是 代码中出现的每次调用编译生成的指令都至关于一个函数体,而不是简单的几条传参指令和call指令。因此,使用函数式宏定义编译生成的目标文件会比较大。
三、定义这种宏要格外当心,若是上面的定义写成#define MAX(a, b) (a>b?a:b),省去内层括号,则宏展开就成了k = (i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。一样道理,这个宏定义的外层 括号也是不能省的,想想为何。
四、调用函数时先求实参表达式的值再传给形参,若是实参表达式有Side Effect,那么这些Side Effect只发生一次。例如MAX(++a, ++b),若是MAX是个真正的函数,a和b只增长一次。但若是MAX是上面那样的宏定义,则要展开成k = ((++a)>(++b)?(++a):(++b)),a和b就不必定是增长一次仍是两次了。
五、即便实参没有Side Effect,使用函数式宏定义也每每会致使较低的代码执行效率。下面举一个极端的例子,也是个颇有意思的例子。
例 21.1. 函数式宏定义
#define MAX(a, b) ((a)>(b)?(a):(b))
int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };
int max(int n)
{
return n == 0 ? a[0] : MAX(a[n], max(n-1));
}
int main(void)
{
max(9);
return 0;
}
这段代码从一个数组中找出最大的数,若是MAX是个真正的函数,这个算法就是从前到后遍历一遍数组,时间复杂度是Θ(n),而如今MAX是这样一个函数式宏定义,思考一下这个算法的时间复杂度是多少?
尽管函数式宏定义和真正的函数相比有不少缺点,但只要当心使用仍是会显著提升代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工做,因 此那些简短而且被频繁调用的函数常常用函数式宏定义来代替实现。例如C标准库的不少函数都提供两种实现,一种是真正的函数实现,一种是宏定义实现,这一点 之后还要详细解释。
函数式宏定义常常写成这样的形式(取自内核代码include/linux/pm.h):
#define device_init_wakeup(dev,val) \
do { \
device_can_wakeup(dev) = !!(val); \
device_set_wakeup_enable(dev,val); \
} while(0)
为何要用do { ... } while(0)括起来呢?不括起来会有什么问题呢?
#define device_init_wakeup(dev,val) \
device_can_wakeup(dev) = !!(val); \
device_set_wakeup_enable(dev,val);
if (n > 0)
device_init_wakeup(d, v);
这样宏展开以后,函数体的第二条语句不在if条件中。那么简单地用{ ... }括起来组成一个语句块不行吗?
#define device_init_wakeup(dev,val) \
{ device_can_wakeup(dev) = !!(val); \
device_set_wakeup_enable(dev,val); }
if (n > 0)
device_init_wakeup(d, v);
else
continue;
问题出在device_init_wakeup(d, v);末尾的;号,若是不容许写这个;号,看起来不像个函数调用,可若是写了这个;号,宏展开以后就有语法错误,if语句被这个;号结束掉了,无法跟 else配对。所以,do { ... } while(0)是一种比较好的解决办法。
若是在一个程序文件中重复定义一个宏,C语言规定这些重复的宏定义必须如出一辙。例如这样的重复定义是容许的:
#define OBJ_LIKE (1 - 1)
#define OBJ_LIKE /* comment */ (1/* comment */-/* comment */ 1)/* comment */
在定义的先后多些空白(这里的空白包括空格、Tab、注释,由于前一步预处理要把注释替换成空格)没有关系,在定义中间连续多个空白等价于一个空白,但在定义中间有空白和没有空白被认为是不一样的,因此这样的重复定义是不容许的:
#define OBJ_LIKE (1 - 1)
#define OBJ_LIKE (1-1)
若是须要从新定义一个宏,和原来的定义不一样,能够先用#undef取消原来的定义,再从新定义,例如:
#define X 3
... /* X is 3 */
#undef X
... /* X has no definition */
#define X 2
... /* X is 2 */
2.2. 内联函数
C99引入一个新关键字inline,用于定义内联函数(inline function)。这种用法在内核代码中很常见,例如include/linux/rwsem.h中:
static inline void down_read(struct rw_semaphore *sem)
{
might_sleep();
rwsemtrace(sem,"Entering down_read");
__down_read(sem);
rwsemtrace(sem,"Leaving down_read");
}
inline关键字告诉编译器,这个函数的调用要尽量快,能够当普通的函数调用实现,也能够用宏展开的办法实现。咱们作个实验,把上一节的例子改一下:
例 21.2. 内联函数
inline int MAX(int a, int b)
{
return a > b ? a : b;
}
int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };
int max(int n)
{
return n == 0 ? a[0] : MAX(a[n], max(n-1));
}
int main(void)
{
max(9);
return 0;
}
按往常的步骤编译而后反汇编:
$ gcc main.c -g
$ objdump -dS a.out
...
int max(int n)
{
8048369: 55 push %ebp
804836a: 89 e5 mov %esp,%ebp
804836c: 83 ec 0c sub $0xc,%esp
return n == 0 ? a[0] : MAX(a[n], max(n-1));
804836f: 83 7d 08 00 cmpl $0x0,0x8(%ebp)
8048373: 75 0a jne 804837f <max+0x16>
8048375: a1 c0 95 04 08 mov 0x80495c0,%eax
804837a: 89 45 fc mov %eax,-0x4(%ebp)
804837d: eb 29 jmp 80483a8 <max+0x3f>
804837f: 8b 45 08 mov 0x8(%ebp),%eax
8048382: 83 e8 01 sub $0x1,%eax
8048385: 89 04 24 mov %eax,(%esp)
8048388: e8 dc ff ff ff call 8048369 <max>
804838d: 89 c2 mov %eax,%edx
804838f: 8b 45 08 mov 0x8(%ebp),%eax
8048392: 8b 04 85 c0 95 04 08 mov 0x80495c0(,%eax,4),%eax
8048399: 89 54 24 04 mov %edx,0x4(%esp)
804839d: 89 04 24 mov %eax,(%esp)
80483a0: e8 9f ff ff ff call 8048344 <MAX>
80483a5: 89 45 fc mov %eax,-0x4(%ebp)
80483a8: 8b 45 fc mov -0x4(%ebp),%eax
}
...
能够看到MAX是做为普通函数调用的。若是指定优化选项编译,而后反汇编:
$ gcc main.c -g -O
$ objdump -dS a.out
...
int max(int n)
{
8048355: 55 push %ebp
8048356: 89 e5 mov %esp,%ebp
8048358: 53 push %ebx
8048359: 83 ec 04 sub $0x4,%esp
804835c: 8b 5d 08 mov 0x8(%ebp),%ebx
return n == 0 ? a[0] : MAX(a[n], max(n-1));
804835f: 85 db test %ebx,%ebx
8048361: 75 07 jne 804836a <max+0x15>
8048363: a1 a0 95 04 08 mov 0x80495a0,%eax
8048368: eb 18 jmp 8048382 <max+0x2d>
804836a: 8d 43 ff lea -0x1(%ebx),%eax
804836d: 89 04 24 mov %eax,(%esp)
8048370: e8 e0 ff ff ff call 8048355 <max>
inline int MAX(int a, int b)
{
return a > b ? a : b;
8048375: 8b 14 9d a0 95 04 08 mov 0x80495a0(,%ebx,4),%edx
804837c: 39 d0 cmp %edx,%eax
804837e: 7d 02 jge 8048382 <max+0x2d>
8048380: 89 d0 mov %edx,%eax
int a[] = { 9, 3, 5, 2, 1, 0, 8, 7, 6, 4 };
int max(int n)
{
return n == 0 ? a[0] : MAX(a[n], max(n-1));
}
8048382: 83 c4 04 add $0x4,%esp
8048385: 5b pop %ebx
8048386: 5d pop %ebp
8048387: c3 ret
...
能够看到,并无call指令调用MAX函数,MAX函数的指令是内联在max函数中的,因为源代码和指令的次序没法对应,max和MAX函数的源代码也交错在一块儿显示。
2.3. #、##运算符和可变参数
在函数式宏定义中,#运算符用于建立字符串,#运算符后面应该跟一个形参(中间能够有空格或Tab),例如:
#define STR(s) # s
STR(hello world)
用cpp命令预处理以后是"hello?world",自动用"号把实参括起来成为一个字符串,而且实参中的连续多个空白字符被替换成一个空格。
再好比:
#define STR(s) #s
fputs(STR(strncmp("ab\"c\0d", "abc", '\4"')
== 0) STR(: @\n), s);
预处理以后是fputs("strncmp("ab\"c\0d", "abc", '\4"') == 0" ": @n", s);,注意若是实参中包含字符常量或字符串,则宏展开以后字符串的界定符"要替换成",字符常量或字符串中的和"字符要替换成\和"。
在宏定义中能够用##运算符把先后两个预处理Token链接成一个预处理Token,和#运算符不一样,##运算符不只限于函数式宏定义,变量式宏定义也能够用。例如:
#define CONCAT(a, b) a##b
CONCAT(con, cat)
预处理以后是concat。再好比,要定义一个宏展开成两个#号,能够这样定义:
#define HASH_HASH # ## #
中间的##是运算符,宏展开时先后两个#号被这个运算符链接在一块儿。注意中间的两个空格是不可少的,若是写成####,会被划分红##和##两个Token,而根据定义##运算符用于链接先后两个预处理Token,不能出如今宏定义的开头或末尾,因此会报错。
咱们知道printf函数带有可变参数,函数式宏定义也能够带可变参数,一样是在参数列表中用...表示可变参数。例如:
#define showlist(...) printf(#__VA_ARGS__)
#define report(test, ...) ((test)?printf(#test):\
printf(__VA_ARGS__))
showlist(The first, second, and third items.);
report(x>y, "x is %d but y is %d", x, y);
预处理以后变成:
printf("The first, second, and third items.");
((x>y)?printf("x>y"): printf("x is %d but y is %d", x, y));
在宏定义中,可变参数的部分用__VA_ARGS__表示,实参中对应...的几个参数能够当作一个参数替换到宏定义中__VA_ARGS__所在的地方。
调用函数式宏定义容许传空参数,这一点和函数调用不一样,经过下面几个例子理解空参数的用法。
#define FOO() foo
FOO()
预处理以后变成foo。FOO在定义时不带参数,在调用时也不容许传参数给它。
#define FOO(a) foo##a
FOO(bar)
FOO()
预处理以后变成:
foobar
foo
FOO在定义时带一个参数,在调用时必须传一个参数给它,若是不传参数则表示传了一个空参数。
#define FOO(a, b, c) a##b##c
FOO(1,2,3)
FOO(1,2,)
FOO(1,,3)
FOO(,,3)
预处理以后变成:
123
12
13
3
FOO在定义时带三个参数,在调用时也必须传三个参数给它,空参数的位置能够空着,但必须给够三个参数,FOO(1,2)这样的调用是错误的。
#define FOO(a, ...) a##__VA_ARGS__
FOO(1)
FOO(1,2,3,)
预处理以后变成:
1
12,3,
|
FOO(1)这个调用至关于可变参数部分传了一个空参数,FOO(1,2,3,)这个调用至关于可变参数部分传了三个参数,第三个是空参数。
gcc有一种扩展语法,若是##运算符用在__VA_ARGS__前面,除了起链接做用以外还有特殊的含义,例如内核代码net/netfilter/nf_conntrack_proto_sctp.c中的:
#define DEBUGP(format, ...) printk(format, ## __VA_ARGS__)
printk这个内核函数至关于printf,也带有格式化字符串和可变参数,因为内核不能调用libc的函数,因此另外实现了一个打印函数。这个 函数式宏定义能够这样调用:DEBUGP("info no. %d", 1)。也能够这样调用:DEBUGP("info")。后者至关于可变参数部分传了一个空参数,但展开后并非printk("info",),而是 printk("info"),当__VA_ARGS是空参数时,##运算符把它前面的,号“吃”掉了。
2.4. 宏展开的步骤
以上举的宏展开的例子都是最简单的,有些宏展开的过程要作屡次替换,例如:
#define sh(x) printf("n" #x "=%d, or %d\n",n##x,alt[x])
#define sub_z 26
sh(sub_z)
sh(sub_z)要用sh(x)这个宏定义来展开,形参x对应的实参是sub_z,替换过程以下:
1. #x要替换成"sub_z"。
2. n##x要替换成nsub_z。
3. 除了带#和##运算符的参数以外,其它参数在替换以前要对实参自己作充分的展开,因此应该先把sub_z展开成26再替换到alt[x]中x的位置。
4. 如今展开成了printf("n" "sub_z" "=%d, or %dn",nsub_z,alt[26]),全部参数都替换完了,这时编译器会再扫描一遍,再找出能够展开的宏定义来展开,假设nsub_z或alt是变量式宏定义,这时会进一步展开。
再举一个例子:
#define x 3
#define f(a) f(x * (a))
#undef x
#define x 2
#define g f
#define t(a) a
t(t(g)(0) + t)(1);
展开的步骤是:
1. 先把g展开成f再替换到#define t(a) a中,获得t(f(0) + t)(1);。
2. 根据#define f(a) f(x * (a)),获得t(f(x * (0)) + t)(1);。
3. 把x替换成2,获得t(f(2 * (0)) + t)(1);。注意,一开始定义x为3,可是后来用#undef x取消了x的定义,又从新定义x为2。当处理到t(t(g)(0) + t)(1);这一行代码时x已经定义成2了,因此用2来替换。还要注意一点,如今获得的t(f(2 * (0)) + t)(1);中仍然有f,但不能再次根据#define f(a) f(x * (a))展开了,f(2 * (0))就是由展开f(0)获得的,这里面再遇到f就不展开了,这样规定能够避免无穷展开(相似于无穷递归),所以咱们能够放心地使用递归定义,例 如#define a a[0],#define a a.member等。
4. 根据#define t(a) a,最终展开成f(2 * (0)) + t(1);。这时不能再展开t(1)了,由于这里的t就是由展开t(f(2 * (0)) + t)获得的,因此不能再展开了。
可变参数宏
void printf(const char* format, …);
直到最近,可变参数表仍是只能应用在真正的函数中,不能使用在宏中。
C99编译器标准终于改变了这种局面,它容许你能够定义可变参数宏(variadic macros),这样你就可使用拥有能够变化的参数表的宏。可变参数宏就像下面这个样子:
#define debug(…) printf(__VA_ARGS__)
缺省号表明一个能够变化的参数表。使用保留名 __VA_ARGS__ 把参数传递给宏。当宏的调用展开时,实际的参数就传递给 printf()了。例如:
Debug(“Y = %d\n”, y);
而处理器会把宏的调用替换成:
printf(“Y = %d\n”, y);
由于debug()是一个可变参数宏,你能在每一次调用中传递不一样数目的参数:
debug(“test”); //一个参数
可变参数宏不被ANSI/ISO C++ 所正式支持。所以,你应当检查你的编译器,看它是否支持这项技术。
#ifdef DEBUG如此定义以后,代码中就能够用dbgprint了,例如dbgprint("aaa %s", __FILE__);。感受这个功能比较Cool :em11:
#define dbgprint(format,args...) \
fprintf(stderr, format, ##args)
#else
#define dbgprint(format,args...)
#endif
#define dgbmsg(fmt,...) \
printf(fmt,__VA_ARGS__)
新的C99规范支持了可变参数的宏
具体使用以下:
如下内容为程序代码:
#include <stdarg.h> #include <stdio.h>
#define LOGSTRINGS(fm, ...) printf(fm,__VA_ARGS__)
int main() { LOGSTRINGS("hello, %d ", 10); return 0; }
但如今彷佛只有gcc才支持。
在1999年版本的ISO C 标准中,宏能够象函数同样,定义时能够带有可变参数。宏的语法和函数的语法相似。下面有个例子:
#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)
这里,‘…’指可变参数。这类宏在被调用时,它(这里指‘…’)被表示成零个或多个符号,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体(macro body)中,那些符号序列集合将代替里面的__VA_ARGS__标识符。更多的信息能够参考CPP手册。
GCC始终支持复杂的宏,它使用一种不一样的语法从而可使你能够给可变参数一个名字,如同其它参数同样。例以下面的例子:
#define debug(format, args...) fprintf (stderr, format, args)
这和上面举的那个ISO C定义的宏例子是彻底同样的,可是这么写可读性更强而且更容易进行描述。
GNU CPP还有两种更复杂的宏扩展,支持上面两种格式的定义格式。
在标准C里,你不能省略可变参数,可是你却能够给它传递一个空的参数。例如,下面的宏调用在ISO C里是非法的,由于字符串后面没有逗号:
debug ("A message")
GNU CPP在这种状况下可让你彻底的忽略可变参数。在上面的例子中,编译器仍然会有问题(complain),由于宏展开后,里面的字符串后面会有个多余的逗号。
为了解决这个问题,CPP使用一个特殊的‘##’操做。书写格式为:
#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
这里,若是可变参数被忽略或为空,‘##’操做将使预处理器(preprocessor)去除掉它前面的那个逗号。若是你在宏调用时,确实提供了一些可变参数,GNU CPP也会工做正常,它会把这些可变参数放到逗号的后面。象其它的pasted macro参数同样,这些参数不是宏的扩展。
#define DEBUG(args) (printf("DEBUG: "), printf args)
if(n != 0) DEBUG(("n is %d\n", n));
明显的缺陷是调用者必须记住使用一对额外的括弧。
gcc 有一个扩展可让函数式的宏接受可变个数的参数。 但这不是标准。另外一种 可能的解决方案是根据参数个数使用多个宏 (DEBUG1, DEBUG2, 等等), 或者用 逗号玩个这样的花招:
#define DEBUG(args) (printf("DEBUG: "), printf(args))
#define _ ,
DEBUG("i = %d" _ i);
C99 引入了对参数个数可变的函数式宏的正式支持。在宏 ``原型" 的末尾加上符号 ... (就像在参数可变的函数定义中), 宏定义中的伪宏 __VA_ARGS__ 就会在调用是 替换成可变参数。
最后, 你老是可使用真实的函数, 接受明肯定义的可变参数