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


在學習第一門編程語言時,就已經介紹了順序分支、條件分支、循環分支。比如循環分支有for、while、do-while語句。在隨后的學校及工作中,如果手工循環一般使用for、while,很少使用do-while,感覺用處不大,但現在看來,do-while大有用途。

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)來包裹他們的邏輯,這樣不管在調用代碼中怎么使用分號和大括號,而該宏總能確保其行為是一致的。

我的心得,這是我最喜歡使用do-while的一處用法,如果你開發了一個庫、甚至就只是一個宏實現提供給別人使用,你不能確信所有人都會按照你的意圖正確使用該實現,有的使用者處處加大括號,也有的人認為你會處理的。因此你需要保證你的實現在各種使用場景下都是表現一致的。另外,就像Scott Meyers在Effective C++中談及的,讓接口容易被正確使用,而不易被誤用

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代碼好的多了。

我的心得,這也是喜歡do-while的第二個用法,非常好。比如有個人認為這樣會導致很多變量要在do-while語句外面提前聲明(這是C++程序反對的一點,用時才聲明),也有人認為還有強大的RAII和智能指針神馬的,不怕內存問題。但是,在某些場景下,要是這些都不能用呢,比如C語言?比如沒有smart ptr?比如維護的是舊代碼?

3. 避免由宏引起的警告

內核中由於不同架構的限制,很多時候會用到空宏,。在編譯的時候,這些空宏會給出warning,為了避免這樣的warning,我們可以使用do{...}while(0)來定義空宏:

#define EMPTYMICRO do{}while(0)

好吧,這種情況我還沒有見到。 

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

如果你有一個復雜的函數,變量很多,而且你不想要增加新的函數,可以使用do{...}while(0),將你的代碼寫在里面,里面可以定義變量而不用考慮變量名會同函數之前或者之后的重復。


這種情況應該是指一個變量多處使用(但每處的意義還不同),我們可以在每個do-while中縮小作用域,比如:

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);    
}

 

注,以上內容來自:

http://www.pixelstech.net/article/1390482950-do-%7B-%7D-while-%280%29-in-macros

http://www.pixelstech.net/article/1350871981-Significance-and-use-of-do%7B-%7Dwhile%280%29-


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM