1.Preprocessor Glue: The ## Operator
预处理连接符:##操作符
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:
#define XNAME(n) x ## n
Then the macro
XNAME(4)
would expand to the following:
x4
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__宏在预编译时会替换成当前的函数名称
#、##和__VA_ARGS__
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的位置。注意这种函数式宏定义和真正的函数调用有什么不同:
1、函数式宏定义的参数没有类型,预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。
2、调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。如果MAX是个真正的函数,那么它的函数体return a > b ? a : b;要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而如果MAX是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是 代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。
3、定义这种宏要格外小心,如果上面的定义写成#define MAX(a, b) (a>b?a:b),省去内层括号,则宏展开就成了k = (i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。同样道理,这个宏定义的外层 括号也是不能省的,想一想为什么。
4、调用函数时先求实参表达式的值再传给形参,如果实参表达式有Side Effect,那么这些Side Effect只发生一次。例如MAX(++a, ++b),如果MAX是个真正的函数,a和b只增加一次。但如果MAX是上面那样的宏定义,则要展开成k = ((++a)>(++b)?(++a):(++b)),a和b就不一定是增加一次还是两次了。
5、即使实参没有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)得到的,所以不能再展开了。
可变参数宏
#define pr_debug(fmt,arg...) \
printk(KERN_DEBUG fmt,##arg)
用可变参数宏(variadic macros)传递可变参数表
你可能很熟悉在函数中使用可变参数表,如:
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++ 所正式支持。因此,你应当检查你的编译器,看它是否支持这项技术。
用GCC和C99的可变参数宏, 更方便地打印调试信息
gcc的预处理提供的可变参数宏定义真是好用:
#ifdef DEBUG如此定义之后,代码中就可以用dbgprint了,例如dbgprint("aaa %s", __FILE__);。感觉这个功能比较Cool :em11:
#define dbgprint(format,args...) \
fprintf(stderr, format, ##args)
#else
#define dbgprint(format,args...)
#endif
下面是C99的方法:
#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才支持。
可变参数的宏里的‘##’操作说明
带有可变参数的宏(Macros with a Variable Number of Arguments)
在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参数一样,这些参数不是宏的扩展。
怎样写参数个数可变的宏
一种流行的技巧是用一个单独的用括弧括起来的的 ``参数" 定义和调用宏, 参数在 宏扩展的时候成为类似 printf() 那样的函数的整个参数列表。
#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__ 就会在调用是 替换成可变参数。
最后, 你总是可以使用真实的函数, 接受明确定义的可变参数如果你需要替换宏, 使用一个 函数和一个非函数式宏, 如 #define printf myprintf。
#define _DEBUGOUT printf
#else
#define _DEBUGOUT
#endif
#define _DEBUGOUT printf
#else
#define _DEBUGOUT(x, ...)
#endif