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__ 就會在調用是 替換成可變參數。
最後, 你老是可使用真實的函數, 接受明肯定義的可變參數