天天看點

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

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 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 <math.h>

#define PR(X, ...) printf("Message" #X ": " _ _VA_ARGS_ _)

    double x = 48;

    double y;

    y = sqrt(x);

    PR(1, "x = %g\n", x);

    PR(2, "x = %.2f, y = %.4f\n", x, y);

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);

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 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

這樣宏調用:

展開後:

程式:

#define PXN(n) printf("x"#n" = %d\n",x##n)

    int XNAME(1)=12;//int x1=12;

    PXN(1);//printf("x1 = %d\n", x1);

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);

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: 

我們想看第二行的表達式展開成什麼樣,可以用gcc的-E選項或cpp指令,盡管這個C程式不合文法,但沒關系,我們隻做預處理而不編譯,不會檢查程式是否符合C文法。 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

就像函數調用一樣,把兩個實參分别替換到宏定義中形參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. 函數式宏定義 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

這段代碼從一個數組中找出最大的數,如果MAX是個真正的函數,這個算法就是從前到後周遊一遍數組,時間複雜度是Θ(n),而現在MAX是這樣一個函數式宏定義,思考一下這個算法的時間複雜度是多少? 

盡管函數式宏定義和真正的函數相比有很多缺點,但隻要小心使用還是會顯著提高代碼的執行效率,畢竟省去了配置設定和釋放棧幀、傳參、傳傳回值等一系列工作,因 此那些簡短并且被頻繁調用的函數經常用函數式宏定義來代替實作。例如C标準庫的很多函數都提供兩種實作,一種是真正的函數實作,一種是宏定義實作,這一點 以後還要詳細解釋。 

函數式宏定義經常寫成這樣的形式(取自核心代碼include/linux/pm.h): 

為什麼要用do { ... } while(0)括起來呢?不括起來會有什麼問題呢? 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

這樣宏展開之後,函數體的第二條語句不在if條件中。那麼簡單地用{ ... }括起來組成一個語句塊不行嗎? 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

問題出在device_init_wakeup(d, v);末尾的;号,如果不允許寫這個;号,看起來不像個函數調用,可如果寫了這個;号,宏展開之後就有文法錯誤,if語句被這個;号結束掉了,沒法跟 else配對。是以,do { ... } while(0)是一種比較好的解決辦法。 

如果在一個程式檔案中重複定義一個宏,C語言規定這些重複的宏定義必須一模一樣。例如這樣的重複定義是允許的: 

在定義的前後多些空白(這裡的空白包括空格、Tab、注釋,因為前一步預處理要把注釋替換成空格)沒有關系,在定義中間連續多個空白等價于一個空白,但在定義中間有空白和沒有空白被認為是不同的,是以這樣的重複定義是不允許的: 

如果需要重新定義一個宏,和原來的定義不同,可以先用#undef取消原來的定義,再重新定義,例如: 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

2.2. 内聯函數  

C99引入一個新關鍵字inline,用于定義内聯函數(inline function)。這種用法在核心代碼中很常見,例如include/linux/rwsem.h中: 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

inline關鍵字告訴編譯器,這個函數的調用要盡可能快,可以當普通的函數調用實作,也可以用宏展開的辦法實作。我們做個實驗,把上一節的例子改一下: 

例 21.2. 内聯函數 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

按往常的步驟編譯然後反彙編: 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

可以看到MAX是作為普通函數調用的。如果指定優化選項編譯,然後反彙編: 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

可以看到,并沒有call指令調用MAX函數,MAX函數的指令是内聯在max函數中的,由于源代碼和指令的次序無法對應,max和MAX函數的源代碼也交錯在一起顯示。 

2.3. #、##運算符和可變參數  

在函數式宏定義中,#運算符用于建立字元串,#運算符後面應該跟一個形參(中間可以有空格或Tab),例如: 

用cpp指令預處理之後是"hello?world",自動用"号把實參括起來成為一個字元串,并且實參中的連續多個空白字元被替換成一個空格。 

再比如: 

預處理之後是fputs("strncmp("ab\"c\0d", "abc", '\4"') == 0" ": @n", s);,注意如果實參中包含字元常量或字元串,則宏展開之後字元串的界定符"要替換成",字元常量或字元串中的和"字元要替換成\和"。 

在宏定義中可以用##運算符把前後兩個預處理Token連接配接成一個預處理Token,和#運算符不同,##運算符不僅限于函數式宏定義,變量式宏定義也可以用。例如: 

預處理之後是concat。再比如,要定義一個宏展開成兩個#号,可以這樣定義: 

中間的##是運算符,宏展開時前後兩個#号被這個運算符連接配接在一起。注意中間的兩個空格是不可少的,如果寫成####,會被劃分成##和##兩個Token,而根據定義##運算符用于連接配接前後兩個預處理Token,不能出現在宏定義的開頭或末尾,是以會報錯。 

我們知道printf函數帶有可變參數,函數式宏定義也可以帶可變參數,同樣是在參數清單中用...表示可變參數。例如: 

預處理之後變成:

在宏定義中,可變參數的部分用__VA_ARGS__表示,實參中對應...的幾個參數可以看成一個參數替換到宏定義中__VA_ARGS__所在的地方。 

調用函數式宏定義允許傳空參數,這一點和函數調用不同,通過下面幾個例子了解空參數的用法。 

預處理之後變成foo。FOO在定義時不帶參數,在調用時也不允許傳參數給它。 

預處理之後變成: 

FOO在定義時帶一個參數,在調用時必須傳一個參數給它,如果不傳參數則表示傳了一個空參數。 

FOO在定義時帶三個參數,在調用時也必須傳三個參數給它,空參數的位置可以空着,但必須給夠三個參數,FOO(1,2)這樣的調用是錯誤的。 

 預處理之後變成: 

<code>1</code>

<code>12,3,</code>

FOO(1)這個調用相當于可變參數部分傳了一個空參數,FOO(1,2,3,)這個調用相當于可變參數部分傳了三個參數,第三個是空參數。 

gcc有一種擴充文法,如果##運算符用在__VA_ARGS__前面,除了起連接配接作用之外還有特殊的含義,例如核心代碼net/netfilter/nf_conntrack_proto_sctp.c中的: 

printk這個核心函數相當于printf,也帶有格式化字元串和可變參數,由于核心不能調用libc的函數,是以另外實作了一個列印函數。這個 函數式宏定義可以這樣調用:DEBUGP("info no. %d", 1)。也可以這樣調用:DEBUGP("info")。後者相當于可變參數部分傳了一個空參數,但展開後并不是printk("info",),而是 printk("info"),當__VA_ARGS是空參數時,##運算符把它前面的,号“吃”掉了。 

2.4. 宏展開的步驟  

以上舉的宏展開的例子都是最簡單的,有些宏展開的過程要做多次替換,例如: 

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是變量式宏定義,這時會進一步展開。 

再舉一個例子: 

宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏
宏定義中的##操作符和... and _ _VA_ARGS_ _用GCC和C99的可變參數宏, 更友善地列印調試資訊可變參數的宏裡的‘##’操作說明怎樣寫參數個數可變的宏

展開的步驟是: 

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的預處理提供的可變參數宏定義真是好用: 

如此定義之後,代碼中就可以用dbgprint了,例如dbgprint("aaa %s", __FILE__);。感覺這個功能比較Cool  :em11: 

下面是C99的方法: 

新的C99規範支援了可變參數的宏

具體使用如下:

 以下内容為程式代碼:

 #include &lt;stdarg.h&gt; #include &lt;stdio.h&gt;

 #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() 那樣的函數的整個參數清單。

明顯的缺陷是調用者必須記住使用一對額外的括弧。

gcc 有一個擴充可以讓函數式的宏接受可變個數的參數。 但這不是标準。另一種 可能的解決方案是根據參數個數使用多個宏 (DEBUG1, DEBUG2, 等等), 或者用 逗号玩個這樣的花招:

C99 引入了對參數個數可變的函數式宏的正式支援。在宏 ``原型" 的末尾加上符号 ... (就像在參數可變的函數定義中), 宏定義中的僞宏 __VA_ARGS__ 就會在調用是 替換成可變參數。

最後, 你總是可以使用真實的函數, 接受明确定義的可變參數

如果你需要替換宏, 使用一個 函數和一個非函數式宏, 如 #define printf myprintf。

其實如果不怕出現no effect statement warning的話,定義成

#ifdef _DEBUG

#define _DEBUGOUT   printf

#else

#define _DEBUGOUT

#endif

如果支援可變參數的話,定義成

#define _DEBUGOUT(x, ...)

關于##在C宏定義中的作用

最近因為工作問題,一直要看Linux的源代碼。對于源碼中宏定義的#一直有點疑惑,發現一個哥們總結的不錯,是以就Ctrl + C and Ctrl + V進來:

核心中有很多的宏定義,在宏定義define中經常看到兩個字元串##和#,這裡把它的用法做一下說明:

    ##是一個連接配接符号,用于把參數連在一起

        例如:

            &gt; #define FOO(arg)   my##arg

        則

            &gt; FOO(abc)

        相當于   myabc

    #是“字元串化”的意思。出現在宏定義中的#是把跟在後面的參數轉換成一個字元串

            &gt; #define STRCPY(dst, src)   strcpy(dst, #src)

            &gt; 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 

                                      &lt;##前面随意加上一些空格&gt;

           #define A2(name, type) type name ##_ ##type ##_type

    結果是## 會把前面的空格去掉完成強連接配接,得到和上面結果相同的宏定義

3. 其他相關 -- 單獨的一個 #

   至于單獨一個#,則表示 對這個變量替換後,再加雙引号引起來。比如

      #define __stringify_1(x)   #x

那麼

      __stringify_1(linux)   &lt;==&gt; "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; */

&lt;==&gt; MODULE_GENERIC_TABLE(usb_device,products)

&lt;==&gt; 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&lt;usb_device_id類型&gt;。這個外部變量

是如何使用的,更多的資訊請參看《probe()過程分析》。

4. 分析方法和驗證方式 -- 編寫一個簡單的C程式

   用宏定義一個變量,同時用直接方式定義一個相同的變量,編譯報告重複定義;

   用宏定義一個變量,直接使用該宏定義的變量名稱,編譯通過且運作結果正确;

   使用printf列印字元串資料。printf("token macro is %s", __stringify_1(a1));

  我看《APUE》的時候信号那一章有這樣的宏定義:我想知道(void (*)())-1  這是 

&gt; 什麼意思,-1和前面的(void (*)())什麼關系,謝謝 

&gt; #define SIG_ERR (void (*)())-1 

&gt; #define SIG_DFL (void (*)())0 

&gt; #define SIG_IGN (void (*)())1 

這個就是一個函數指針類型聲明,将後面的整數-1、0和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);

#    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) &gt; (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) 前加#@,将标記轉換為相應的字元,注意:僅對單一标記轉換有效(了解有誤?)

 則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&amp;&amp;o-&gt;isA(#thisClass))\

    return static_cast&lt;thisClass*&gt;(o);\

   return NULL;\

  }

5. 用于條件編譯:(常用形式)

 #ifndef _AAA_H

 #define _AAA_H

 //c/c++代碼

 #endif

6. 一些注意事項:

  1) 不能重複定義.除非定義完全相同.#define A(x) … 和#define A 是重複定義.

  2) 可以隻定義符号,不定義值.如#define AAA