你想知道的do{...}while(0)的作用,都在這裡了

2023-02-22 06:01:49

0、引言        

        我們在嵌入式開發的過程中,經常可以碰到在一些宏定義或者是程式碼段中使用了do {...} while(0)的語句,從語意上理解,do {...} while(0)內的邏輯就只執行一次,並沒有迴圈執行,粗略看來,似乎畫蛇添足了,那麼為什麼還需要在只執行一次的邏輯外面加上一層do {...} while(0)語句呢?實際上,在這些邏輯中使用do {...} while(0)的作用遠大於美化你的程式碼,下面就來看看實際的使用場景。


1、用於定義一個作用域,避免替換的時候出錯

        我們都知道,在程式中如果一些常數引數或者程式碼語句反覆出現,就可以使用宏定義來替代。預處理階段,對程式中所有出現的「宏名」,前處理器都會用宏定義中的字串替代,這稱為「宏替換」或「宏展開」。

        這樣做可提高程式的通用性和易讀性,減少不一致性,一個較好的宏名可以更好的讓讀者理解常數引數的含義;同時程式易於修改,我們僅需要改變一個宏定義,就可以改變整個程式中出現的所有該常數或者語句。

        但是有時可能程式程式碼段中,出現多條語句重複連續的使用,這樣我們就可以嘗試使用一個複雜的宏來替換。你有可能會這樣定義:

1 #define REPLACE_FUN() funA(); funB()

   本意是在程式中當出現funA()和funB()多條語句連續使用時,使用REPLACE_FUN()來替換。

1 if(判斷條件)
2     REPLACE_FUN();

        但是實際上在預處理的時候,宏展開替換後變成了:

1 if(判斷條件)
2    funA();
3 funB();   //此處funB()一定會執行,造成邏輯錯誤

        可以看出,funB()不會按照判斷條件才去執行。而是變成了一條獨立的語句,而如果在宏中使用括號:

1 #define REPLACE_FUN() {funA(); funB();}

        我們一般的程式碼習慣都會在語句的末尾加上分號,因此也會出錯:

1 if(判斷條件)
2     REPLACE_FUN();
3 //宏展開後為:
4 if(判斷條件)
5 {
6     funA();
7     funB();
8 };    //此處替換後多一個分號;導致編譯報錯

        因此,針對這種多條重複語句的連續使用,如果想用宏替換實現這個作用域的功能,就可以考慮使用do {...} while(0)語句:

 1 define REPLACE_FUN() \
 2         do{ \
 3             funA();\
 4             funB();\
 5           }while(0)\
 6 //宏展開前為:         
 7 if(判斷條件)
 8     REPLACE_FUN();
 9 //宏展開後為:
10 if(判斷條件)
11      do{
12           funA();
13           funB();
14      }while(0);    //根據判斷條件,正確執行了一次邏輯


2、避免goto語句的使用

        goto語句也稱為無條件轉移語句,使用後可以從多重回圈或者多個判斷中直接跳出。對於如下例子:

 1 void fun(int a)
 2 {
 3    if(1 == a)
 4    {
 5        ...//todo
 6        goto exit;
 7    }  
 8    if(2 == a)
 9    {
10      ...//todo
11      goto exit;
12    }
13 exit:
14    ...//todo
15    printf("a is error"\n);
16 }

        但是為了程式結構的清晰,還是要儘量限制goto語句的使用,我們可以使用do {...} while(0)結構配合break跳出單層的迴圈的方法來替代這種goto的用法。

 1 int fun(int a)
 2 {
 3    do{
 4        if(1 == a)
 5        {
 6          ...//todo
 7          break;
 8        }  
 9        if(2 == a)
10        {
11          ...//todo
12          break;
13        }
14    }while(0);
15    ...//todo
16    printf("a is error"\n);
17 }

3、定義一個單獨的函數塊來實現複雜的操作

        當某個函數程式功能較為複雜,在該函數的程式碼段中如果不再單獨定義一個函數實現部分邏輯,可以使用do {...} while(0)作為一個程式碼塊,將想要實現的邏輯放在do {...} while(0)中,同時在該在do {...} while(0)程式碼塊中定義的變數,可以不用考慮和函數之前或者之後的變數名重複衝突的問題。但是為了程式碼的易讀性,還是儘量宣告不同的變數名。

 1 int a;
 2 char b;
 3 int func()
 4 {
 5     int a = 3;
 6     char b = 5;
 7     do{
 8         int a;
 9         char b;
10         ......//todo
11     }while(0);    
12 }

4、避免空宏的警告

        有的時候,程式為了不同的平臺移植或者不同架構的限制,很多時候會先定義空宏,後續再根據實際的需要看是否定義具體內容。但是在編譯的時候,這些空宏可能會給出warning,為了避免這樣的warning,我們可以使用do{...}while(0)來定義空宏,這種情況不太常見,因為有很多編譯器已經支援空宏。

1 //空宏
2 #define EMPTY_FUN
3 //增加do{...}while(0)來定義空宏
4 #define EMPTY_FUN do{}while(0) //避免了可能的編譯warning

更多技術內容和書籍資料獲取,入群技術交流敬請關注「明解嵌入式」