前言:C語言中如何使用宏C(和C++)中的宏(Macro)屬于編譯器預處理的范疇,屬于編譯期概念(而非運行期概念),下面對常遇到的宏的使用問題做了簡單總結,
關于#和##

在C語言的宏中,#的功能是將其后面的宏引數進行字串化操作(Stringfication),簡單說就是在對它所參考的宏變數通過替換后在其左右各加上一個雙引號,比如下面代碼中的宏:
#define WARN_IF(EXP) do{ if (EXP) fprintf(stderr, "Warning: " #EXP "\n"); } while(0)
那么實際使用中會出現下面所示的替換程序:
WARN_IF (divider == 0);被替換為do {if (divider == 0)fprintf(stderr, "Warning" "divider == 0" "\n");} while(0);
這樣每次divider(除數)為0的時候便會在標準錯誤流上輸出一個提示資訊,
而##被稱為連接符(concatenator),用來將兩個Token連接為一個Token,注意這里連接的物件是Token就行,而不一定是宏的變數,比如你要做一個選單項命令名和函式指標組成的結構體的陣列,并且希望在函式名和選單項命令名之間有直觀的、名字上的關系,那么下面的代碼就非常實用:
struct command
{
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME ## _command }
// 然后你就用一些預先定義好的命令來方便的初始化一個command結構的陣列了:
struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}
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) { /* line 1 */ /* line 2 */ /* line 3 */ }
//...
if (condition())
MY_MACRO(a);
else
{...}
這樣會由于多出的那個分號產生編譯錯誤,為了避免這種情況出現同時保持MY_MACRO(x);的這種寫法,我們需要把宏定義為這種形式:
#define MY_MACRO(x) do {
/* line 1 */ /* line 2 */ /* line 3 */ } 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) ({ typeof (X) x_ = (X); typeof (Y) y_ = (Y); (x_ < y_) ? x_ : y_; })
({...})的作用是將內部的幾條陳述句中最后一條的值回傳,它也允許在內部宣告變數(因為它通過大括號組成了一個區域Scope),
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/195900.html
標籤:C
上一篇:程式員說:為什么喜歡大量使用 if……else if替代switch?
下一篇:隨記-大疆面試題
