天天看點

宏定義中的#,##,...,do{}while(0),__VA_ARGS__

宏定義中的#,##

1.在一個預處理器宏中的參數前面使用一個#,預處理器會把這個參數轉換為一個字元數組

#define syslog(a) fprintf(stderr,"Warning: " #a"\n");

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

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

可變參數的宏定義

在寫程式輸出的時候,總會遇到一些參數可變的輸出,為了友善那麼可變參數宏會是一個選擇。

在C99中規定宏也可以像函數一樣帶可變的參數,如:

#define dmsg(format, ...) fprintf(stderr, format, __VA_ARGS__)

其中,...表示參數可變,__VA_ARGS__在預進行中為實際的參數集所替換 

GCC中同時支援如下的形式

#define dmsg(format, args...) fprintf(stderr, format, args)

其用法和上面的基本一緻,隻是參數符号有變化

有一點需要注意,上述的宏定義不能省略可變參數,盡管你可以傳遞一個空參數, 這裡有必要提到"##"連接配接符号的用法。

"##"的作用是對token進行連接配接,在上例中,format、__VA_ARGS__、args即是token,

如果token為空,那麼不進行連接配接,是以允許省略可變參數(__VA_ARGS__和args), 對上述變參宏做如下修改

#define dmsg(format, ...)     fprintf(stderr, format, ##__VA_ARGS__)

或者

#define dmsg(format, args...) fprintf(stderr, format, ##args)

上述的變參宏定義不僅能自定義輸出格式,而且配合#ifdef #else #endif在輸出管理上也很友善,

比如調試時輸出調試資訊,正式釋出時則不輸出,可以這樣

#ifdef _ENABLE_DEBUG_

#define dmsg(format, args...) fprintf(stderr, "log: "format"\n", ##args)

#else

#define dmsg(format, args ...)

#endif

例如:

int  main()

{

    dmsg();

    dmsg("%s %s","hello","world");

    dmsg("%s %s %s","hello","linux","world");

    dmsg("%s %s %d","this","count is",123);

    dmsg0;

}

log: hello world

log: hello linux world

log: this count is 123

宏定義中的do...while(0)

最近在考哪代碼時經常碰到do...while(0)這種「奇怪」的定義方式,例如:

#define msg(flags, args ...) do { if (msg_test(flags)) {x_msg((flags), args);} EXIT_FATAL(flags); } while (false)

看到之後很疑惑,為什麼這樣寫呢那個?豈不是多次一舉

1. 空的宏定義避免warning:

  #define dmsg() do{}while(0)

2.存在一個獨立的block,可以用來進行變量定義,進行比較複雜的實作

 使用do{…}while(0)構造後的宏定義,該宏就相當于一個語句塊,我們可以在該語句塊中做比較複雜的操作,而且不容易影響到宏外面的内容。

3. 保證宏作為一個整體使用

 使用do{…}while(0)構造後的宏定義不會受到大括号、分号等的影響,保證作為一個整體來是實作。(這一點在宏前使用判斷語句的情況尤為重要)

  我們看一個例子,在不使用do{…}while(0)時,如下,

有如下宏定義:

#define SKIP_SPACES(p, limit)  \

     { char *lim = (limit);         \

       while (p < lim) {            \

         if (*p++ != ' ') {         \

           p--; break; }}}

假設有如下一段代碼:

if (*p != 0)

   SKIP_SPACES (p, lim);

else ...

一編譯,GCC報error: ‘else’ without a previous ‘if’。原來這個看似是一個函數的宏被展開後是一段大括号括起來的代碼塊,加上分号之後這個if邏輯塊就結束了,是以編譯器發現這個else沒有對應的if。

這個問題一般用do ... while(0)的形式來解決:

#define SKIP_SPACES(p, limit)     \

     do { char *lim = (limit);         \

          while (p < lim) {            \

            if (*p++ != ' ') {         \

              p--; break; }}}          \

     while (0)

展開後就成了

    do ... while(0);

這樣就消除了分号吞噬問題。

4. 可以在宏中更好的跳轉

  這中情況主要出現比較複雜的宏定義中,當我們使用希望在某中情況中跳出宏的執行,do{…}while(0)就提供了很好的方式。我們可以在do{…}while(0)結構的宏定義中使用break語句,跳出宏。

    #define SKIP_SPACES(p, limit)     \

本文轉自 Linux_woniu 51CTO部落格,原文連結:http://blog.51cto.com/linuxcgi/1967062

繼續閱讀