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

轉自https://www.jianshu.com/p/99efda8dfec9
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§ do { if§ { delete §; § = 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);
}

作者:IvanRunning
鏈接:https://www.jianshu.com/p/99efda8dfec9
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章