fw: do{...}while(0)的妙用

轉自:https://www.jianshu.com/p/99efda8dfec9

do{...}while(0)的妙用

2018.05.07 16:38:26 字數 1,030 閱讀 17,406

1.幫助定義複雜的宏以避免錯誤

舉例來說,假設你需要定義這樣一個宏:
#define DOSOMETHING() foo1(); foo2();
這個宏的本意是,當調用DOSOMETHING()時,函數foo1()和foo2()都會被調用。但是如果你在調用的時候這麼寫:

 
if(a>0)
    DOSOMETHING();

因爲宏在預處理的時候會直接被展開,你實際上寫的代碼是這個樣子的:

 
if(a>0)
    foo1();
    foo2();

這就出現了問題,因爲無論a是否大於0,foo2()都會被執行,導致程序出錯。
那麼僅僅使用{}將foo1()和foo2()包起來行麼?比如:
#define DOSOMETHING() { foo1(); foo2(); }
我們在寫代碼的時候都習慣在語句右面加上分號,如果在宏中使用{},代碼編譯展開後宏就相當於這樣寫了:“{...};”,展開後就是這個樣子:

 
if(a>0)
{
    foo1();
    foo2();
};

很明顯,這是一個語法錯誤(大括號後多了一個分號)。
現在的編譯器會自動檢測自動忽略分號,不會報錯,但是我們還是希望能跑在老的編譯器上。

在沒有do/while(0)的情況下,在所有可能情況下,期望我們寫的多語句宏總能有正確的表現幾乎是不可能的。
如果我們使用do{...}while(0)來定義宏,即:

 
#define DOSOMETHING() \
        do{ \
          foo1();\
          foo2();\
        }while(0)\

這樣,宏被展開後,上面的調用語句纔會保留初始的語義。do能確保大括號裏的邏輯能被執行,而while(0)能確保該邏輯只被執行一次,就像沒有循環語句一樣。

總結:在Linux和其它代碼庫裏的,很多宏實現都使用do/while(0)來包裹他們的邏輯,這樣不管在調用代碼中怎麼使用分號和大括號,而該宏總能確保其行爲是一致的。
cocos2d-x中大量使用了這種宏定義:
#define CC_SAFE_DELETE(p) do { if(p) { delete (p); (p) = 0; } } while(0)

2. 避免使用goto控制程序流

在一些函數中,我們可能需要在return語句之前做一些清理工作,比如釋放在函數開始處由malloc申請的內存空間,使用goto總是一種簡單的方法:

 
int foo()
{
    somestruct *ptr = malloc(...);

    dosomething...;
    if(error)
        goto END;
    dosomething...;
    if(error)
        goto END;
    dosomething...;
END:
    free(ptr);
    return 0;
}

但由於goto不符合軟件工程的結構化,而且有可能使得代碼難懂,所以很多人都不倡導使用,這個時候我們可以使用do{...}while(0)來做同樣的事情:

 
int foo()
{
    somestruct *ptr = malloc(...);
    do
    {
        dosomething...;
        if(error)
            break;
        dosomething...;
        if(error)
            break;
        dosomething...;
    }
    while(0);

    free(ptr);
    return 0;
}

這裏將函數主體部分使用do{...}while(0)包含起來,使用break來代替goto,後續的清理工作在while之後,現在既能達到同樣的效果,而且代碼的可讀性、可維護性都要比上面的goto代碼好的多了。

我經常使用這個種技能在Lua裏,Lua不支持do{...}while(0)語法,但是Lua有一種類似的語法repeat...until,僞代碼如下:

 
repeat
  dosomething...
  if error then
    break;
  end
  dosomething...;
  if error then
    break;
  end      
  dosomething...;
until (1);
print("break repeat");

這樣和do{...}while(0)一樣,也保證了只執行一次,可以用break調出循環。

3. 避免由宏引起的警告

內核中由於不同架構的限制,很多時候會用到空宏,。在編譯的時候,這些空宏會給出warning,爲了避免這樣的warning,我們可以使用do{...}while(0)來定義空宏:
#define EMPTYMICRO do{}while(0)
這種情況不太常見,因爲有很多編譯器,已經支持空宏。

4. 定義單一的函數塊來完成複雜的操作

如果你有一個複雜的函數,變量很多,而且你不想要增加新的函數,可以使用do{...}while(0),將你的代碼寫在裏面,裏面可以定義變量而不用考慮變量名會同函數之前或者之後的重複。
但是我不建議這樣做,儘量聲明不同的變量名,以便於後續開發人員閱讀。

 
int key;
string value;
int func()
{
    int key = GetKey();
    string value = GetValue();
    dosomething for key,value;
    do{
        int key;string value;
        dosomething for this key,value;
    }while(0);    
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章