C/C++中宏使用(關於## 和...變參宏)

今天看ffmpeg源碼,看到一坨一坨的

#define REGISTER_HWACCEL(X,x) { /
          extern AVHWAccel ff_##x##_hwaccel; /
          if(CONFIG_##X##_HWACCEL) av_register_hwaccel(&ff_##x##_hwaccel); }

#define REGISTER_ENCODER(X,x) { /
          extern AVCodec ff_##x##_encoder; /
          if(CONFIG_##X##_ENCODER)  avcodec_register(&ff_##x##_encoder); }
當時就傻了,不知此爲何物,杯具。。。

原帖地址:http://hi.baidu.com/a3630623/blog/item/2bedc108927f6ad663d9864e.html

有空再回來看

 

1、 條件 #include
2、條件編譯 #ifdef #ifndef #if expression
3、定義常量 #define
4、可變參數宏
5、宏組合:#和##
注意:
(1)宏名一般用大寫
(2)使用宏可提高程序的通用性和易讀性,減少不一致性,減少輸入錯誤和便於修改。
(3)預處理是在編譯之前的處理,而編譯工作的任務之一就是語法檢查,預處理不做語法檢查。

(4)宏定義末尾不加分號;

(5)宏定義寫在函數的花括號外邊,作用域爲其後的程序,通常在文件的最開頭。

(6)可以用#undef命令終止宏定義的作用域

(7)宏定義可以嵌套
(8)字符串""中永遠不包含宏

(9)宏定義不分配內存,變量定義分配內存。
帶參數宏:
(1)實參如果是表達式容易出問題

(2)宏名和參數的括號間不能有空格

(3)宏替換隻作替換,不做計算,不做表達式求解

(4)函數調用在編譯後程序運行時進行,並且分配內存。宏替換在編譯前進行,不分配內存

(5)宏的啞實結合不存在類型,也沒有類型轉換。

(6)函數只有一個返回值,利用宏則可以設法得到多個值

(7)宏展開使源程序變長,函數調用不會

(8)宏展開不佔運行時間,只佔編譯時間,函數調用佔運行時間(分配內存、保留現場、值傳遞、返回值)
關於#和##

在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)。

發佈了36 篇原創文章 · 獲贊 5 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章