宏定義中的##操作符和... and _ _VA_ARGS_ _
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)得到的,所以不能再展開了。
可變參數宏
在 GNU C 中,宏可以接受可變數目的參數,就象函數一樣,例如:
#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。
其實如果不怕出現no effect statement warning的話,定義成 #ifdef _DEBUG
#define _DEBUGOUT printf
#else
#define _DEBUGOUT
#endif 如果支持可變參數的話,定義成 #ifdef _DEBUG
#define _DEBUGOUT printf
#else
#define _DEBUGOUT(x, ...)
#endif 關于##在C宏定義中的作用
最近因為工作問題,一直要看Linux的源代碼。對于源碼中宏定義的#一直有點疑惑,發現一個哥們總結的不錯,所以就Ctrl + C and Ctrl + V進來:
內核中有很多的宏定義,在宏定義define中經常看到兩個字符串##和#,這里把它的用法做一下說明:
##是一個連接符號,用于把參數連在一起
例如:
> #define FOO(arg) my##arg
則
> FOO(abc)
相當于 myabc
#是“字符串化”的意思。出現在宏定義中的#是把跟在后面的參數轉換成一個字符串
例如:
> #define STRCPY(dst, src) strcpy(dst, #src)
則
> STRCPY(buff, abc)
相當于 strcpy(buff, "abc")
另外,如果##后的參數本身也是一個宏的話,##會阻止這個宏的展開 。
#define STRCPY(a, b) strcpy(a ## _p, #b)
int main()
{
char var1_p[20];
char var2_p[30];
strcpy(var1_p, "aaaa");
strcpy(var2_p, "bbbb");
STRCPY(var1, var2);
STRCPY(var2, var1);
printf("var1 = %s\n", var1_p);
printf("var2 = %s\n", var2_p);
return 0;
/* 注意這里 */
STRCPY(STRCPY(var1,var2),var2);
/* 這里是否會展開為: strcpy(strcpy(var1_p,"var2")_p,"var2“)?
* 答案是否定的:
* 展開結果將是: strcpy(STRCPY(var1,var2)_p,"var2")
* ## 阻止了參數的宏展開!
* 如果宏定義里沒有用到 # 和 ##, 宏將會完全展開
*/
}
/////////////////////////////////////////////////////////////////////////
tell you about ## in common text
關于記號粘貼操作符(token paste operator): ##
1. 簡單的說,“##”是一種分隔連接方式,它的作用是先分隔,然后進行強制連接。
其中,分隔的作用類似于空格。我們知道在普通的宏定義中,預處理器一般把空格
解釋成分段標志,對于每一段和前面比較,相同的就被替換。但是這樣做的結果是,
被替換段之間存在一些空格。如果我們不希望出現這些空格,就可以通過添加一些
##來替代空格。
另外一些分隔標志是,包括操作符,比如 +, -, *, /, [,], ...,所以盡管下面的
宏定義沒有空格,但是依然表達有意義的定義: define add(a, b) a+b
而其強制連接的作用是,去掉和前面的字符串之間的空格,而把兩者連接起來。
2. 舉列 -- 試比較下述幾個宏定義的區別
#define A1(name, type) type name_##type##_type 或
#define A2(name, type) type name##_##type##_type
A1(a1, int); /* 等價于: int name_int_type; */
A2(a1, int); /* 等價于: int a1_int_type; */
解釋:
1) 在第一個宏定義中,"name"和第一個"_"之間,以及第2個"_"和第二個
"type"之間沒有被分隔,所以預處理器會把name_##type##_type解釋成3段:
“name_”、“type”、以及“_type”,這中間只有“type”是在宏前面出現過
的,所以它可以被宏替換。
2) 而在第二個宏定義中,“name”和第一個“_”之間也被分隔了,所以
預處理器會把name##_##type##_type解釋成4段:“name”、“_”、“type”
以及“_type”,這其間,就有兩個可以被宏替換了。
3) A1和A2的定義也可以如下:
#define A1(name, type) type name_ ##type ##_type
<##前面隨意加上一些空格>
#define A2(name, type) type name ##_ ##type ##_type
結果是## 會把前面的空格去掉完成強連接,得到和上面結果相同的宏定義
3. 其他相關 -- 單獨的一個 #
至于單獨一個#,則表示 對這個變量替換后,再加雙引號引起來。比如
#define __stringify_1(x) #x
那么
__stringify_1(linux) <==> "linux"
所以,對于MODULE_DEVICE_TABLE
1) #define MODULE_DEVICE_TABLE(type,name)
MODULE_GENERIC_TABLE(type##_device,name)
2) #define MODULE_GENERIC_TABLE(gtype,name)
extern const struct gtype##_id __mod_##gtype##_table
__attribute__ ((unused, alias(__stringify(name))))
得到
MODULE_DEVICE_TABLE(usb, products)
/*notes: struct usb_device_id products; */
<==> MODULE_GENERIC_TABLE(usb_device,products)
<==> extern const struct usb_device_id __mod_usb_device_table
__attribute__ ((unused, alias("products")))
注意到alias attribute需要一個雙引號,所以在這里使用了__stringify(name)來
給name加上雙引號。另外,還注意到一個外部變量"__mod_usb_device_table"被alias
到了本驅動專用的由用戶自定義的變量products<usb_device_id類型>。這個外部變量
是如何使用的,更多的信息請參看《probe()過程分析》。
4. 分析方法和驗證方式 -- 編寫一個簡單的C程序
用宏定義一個變量,同時用直接方式定義一個相同的變量,編譯報告重復定義;
用宏定義一個變量,直接使用該宏定義的變量名稱,編譯通過且運行結果正確;
使用printf打印字符串數據。printf("token macro is %s", __stringify_1(a1));
我看《APUE》的時候信號那一章有這樣的宏定義:我想知道(void (*)())-1 這是
> 什么意思,-1和前面的(void (*)())什么關系,謝謝
> #define SIG_ERR (void (*)())-1
> #define SIG_DFL (void (*)())0
> #define SIG_IGN (void (*)())1
這個純粹是C語言問題。
寫成這樣可能會好理解一點:
typedef void (*sig_handler_prototype)();
#define SIG_ERR (sig_handler_prototype)-1
#define SIG_DFL (sig_handler_prototype)0
#define SIG_IGN (sig_handler_prototype)-1
typedef void (*sig_handler_prototype)(int);
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/bluecll/archive/2008/11/09/3254764.aspx
# define PDEBUG(fmt, args...) printk( KERN_DEBUG "DEMO: " fmt, ## args)#else//usr space
# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
##args表示如果args為空則消除前面的逗號
define小結
ajumail 發表于 2006-11-10
1. 定義簡單的常數:定義常量,便于修改(切不可在后面加上分號!)
#define N 1000
等效于 const int N = 1000; 但略有不同,define只是簡單替換,而不是作為一個量來使用.
2. 定義簡單的函數:注意多使用括號
#define MAX(x, y) ((x) > (y)) ? (x) : (y)
3. 定義單行宏:主要有以下三種用法.
1) 前加##或后加##,將標記作為一個合法的標識符的一部分.注意,不是字符串.多用于多行的宏定義中.例如:
#define A(x) T_##x
則 int A(1) = 10; //等效于int T_1 = 10;
#define A(x) Tx##__
則 int A(1) = 10; //等效于int T1__ = 10;
2) 前加#@,將標記轉換為相應的字符,注意:僅對單一標記轉換有效(理解有誤?)
#define B(x) #@x
則B(a)即’a’,B(1)即’1’.但B(abc)卻不甚有效.
3) 前加#,將標記轉換為字符串.
#define C(x) #x
則C(1+1) 即 ”1+1”.
4. 定義多行宏:注意斜杠的使用,最后一行不能用斜杠.
#define DECLARE_RTTI(thisClass, superClass)\
virtual const char* GetClassName() const\
{return #thisClass;}\
static int isTypeOf(const char* type)\
{\
if(!strcmp(#thisClass, type)\
return 1;\
return superClass::isTypeOf(type);\
return 0;\
}\
virtual int isA(const char* type)\
{\
return thisClass::isTypeOf(type);\
}\
static thisClass* SafeDownCast(DitkObject* o)\
{\
if(o&&o->isA(#thisClass))\
return static_cast<thisClass*>(o);\
return NULL;\
}
5. 用于條件編譯:(常用形式)
#ifndef _AAA_H
#define _AAA_H
//c/c++代碼
#endif
6. 一些注意事項:
1) 不能重復定義.除非定義完全相同.#define A(x) … 和#define A 是重復定義.
2) 可以只定義符號,不定義值.如#define AAA