c語言中如何使用巨集c(和c++)中的巨集(macro)屬於編譯器預處理的範疇,屬於編譯期概念(而非執行期概念)。下面對常遇到的巨集的使用問題做了簡單總結。
關於#和##
在c語言的巨集中,#的功能是將其後面的巨集引數進行字串化操作(stringfication),簡單說就是在對它所引用的巨集變數 通過替換後在其左右各加上乙個雙引號。比如下面**中的巨集:
#define warn_if(exp) do while(0)
那麼實際使用中會出現下面所示的替換過程:
warn_if (divider == 0);
被替換為
do while(0);
這樣每次divider(除數)為0的時候便會在標準錯誤流上輸出乙個提示資訊。
而##被稱為連線符(concatenator),用來將兩個token連線為乙個token。注意這裡連線的物件是token就行,而不一定 是巨集的變數。比如你要做乙個選單項命令名和函式指標組成的結構體的陣列,並且希望在函式名和選單項命令名之間有直觀的、名字上的關係。那麼下面的**就非常實用:
struct command
;#define command(name)
// 然後你就用一些預先定義好的命令來方便的初始化乙個command結構的陣列了:
struct command commands =
command巨集在這裡充當乙個**生成器的作用,這樣可以在一定程度上減少**密度,間接地也可以減少不留心所造成的錯誤。我們還可以n個##符號連線 n+1個token,這個特性也是#符號所不具備的。比如:
#define link_multiple(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type link_multiple(name,company,position,salary);
// 這裡這個語句將展開為:
// typedef struct _record_type name_company_position_salary;
關於...的使用
...在c巨集中稱為variadic macro,也就是變參巨集。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__va_args__)
// 或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)
第乙個巨集中由於沒有對變參起名,我們用預設的巨集__va_args__來替代它。第二個巨集中,我們顯式地命名變參為args,那麼我們在巨集定義中就可以用args來代指變參了。同c語言的stdcall一樣,變參必須作為參數列的最有一項出現。當上面的巨集中我們只能提供第乙個引數templt時,c標準要求我們必須寫成:
myprintf(templt,);
的形式。這時的替換過程為:
myprintf("error!/n",);
替換為:
fprintf(stderr,"error!/n",);
這是乙個語法錯誤,不能正常編譯。這個問題一般有兩個解決方法。首先,gnu cpp提供的解決方法允許上面的巨集呼叫寫成:
myprintf(templt);
而它將會被通過替換變成:
fprintf(stderr,"error!/n",);
很明顯,這裡仍然會產生編譯錯誤(非本例的某些情況下不會產生編譯錯誤)。除了這種方式外,c99和gnu cpp都支援下面的巨集定義方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__var_args__)
這時,##這個連線符號充當的作用就是當__var_args__為空的時候,消除前面的那個逗號。那麼此時的翻譯過程如下:
myprintf(templt);
被轉化為:
fprintf(stderr,templt);
這樣如果templt合法,將不會產生編譯錯誤。 這裡列出了一些巨集使用中容易出錯的地方,以及合適的使用方式。
錯誤的巢狀-misnesting
巨集的定義不一定要有完整的、配對的括號,但是為了避免出錯並且提高可讀性,最好避免這樣使用。
由操作符優先順序引起的問題-operator precedence problem
由於巨集只是簡單的替換,巨集的引數如果是復合結構,那麼通過替換之後可能由於各個引數之間的操作符優先順序高於單個引數內部各部分之間相互作用的操作符優先順序,如果我們不用括號保護各個巨集引數,可能會產生預想不到的情形。比如:
#define ceil_div(x, y) (x + y - 1) / y
那麼
a = ceil_div( b & c, sizeof(int) );
將被轉化為:
a = ( b & c + sizeof(int) - 1) / sizeof(int);
// 由於+/-的優先順序高於&的優先順序,那麼上面式子等同於:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
這顯然不是呼叫者的初衷。為了避免這種情況發生,應當多寫幾個括號:
#define ceil_div(x, y) (((x) + (y) - 1) / (y))
消除多餘的分號-semicolon swallowing
通常情況下,為了使函式模樣的巨集在表面上看起來像乙個通常的c語言呼叫一樣,通常情況下我們在巨集的後面加上乙個分號,比如下面的帶參巨集:
my_macro(x);
但是如果是下面的情況:
#define my_macro(x)
//...
if (condition())
my_macro(a);
else
這樣會由於多出的那個分號產生編譯錯誤。為了避免這種情況出現同時保持my_macro(x);的這種寫法,我們需要把巨集定義為這種形式:
#define my_macro(x) do while(0)
這樣只要保證總是使用分號,就不會有任何問題。
duplication of side effects
這裡的side effect是指巨集在展開的時候對其引數可能進行多次evaluation(也就是取值),但是如果這個巨集引數是乙個函式,那麼就有可能被呼叫多次從而達到不一致的結果,甚至會發生更嚴重的錯誤。比如:
#define min(x,y) ((x) > (y) ? (y) : (x))
//...
c = min(a,foo(b));
這時foo()函式就被呼叫了兩次。為了解決這個潛在的問題,我們應當這樣寫min(x,y)這個巨集:
#define min(x,y) ()
()的作用是將內部的幾條語句中最後一條的值返回,它也允許在內部宣告變數(因為它通過大括號組成了乙個區域性scope)。
C語言巨集定義 連線符和 符的使用
c語言中如何使用巨集c 和c 中的巨集 macro 屬於編譯器預處理的範疇,屬於編譯期概念 而非執行期概念 下面對常遇到的巨集的使用問題做了簡單總結。在c語言的巨集中,的功能是將其後面的巨集引數進行字串化操作 stringfication 簡單說就是在對它所引用的巨集變數通過替換後在其左右各加上乙個...
C語言巨集定義 連線符和 符的使用
c語言中如何使用巨集c 和c 中的巨集 macro 屬於編譯器預處理的範疇,屬於編譯期概念 而非執行期概念 下面對常遇到的巨集的使用問題做了簡單總結。在c語言的巨集中,的功能是將其後面的巨集引數進行字串化操作 stringfication 簡單說就是在對它所引用的巨集變數通過替換後在其左右各加上乙個...
C語言巨集定義 連線符和 符的使用
連線符號由兩個井號組成,其功能是在帶引數的巨集定義中將兩個子串 token 聯接起來,從而形成乙個新的子串。但它不可以是第乙個或者最後乙個子串。所謂的子串 token 就是指編譯器能夠識別的最小語法單元。簡單的說,是一種分隔連線方式,它的作用是先分隔,然後進行強制連線。其中,分隔的作用類似於空格。我...