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
