-
1 # 繁星落石
-
2 # IT劉小虎
C語言中的 define 宏定義可以像函式那樣接收引數(這種宏定義常被稱作“函式式宏定義”),不過不能像函式那樣提供引數的型別檢查,這個特點在有些程式設計師看來是不安全的。
C語言中的“函式式宏定義”但是,函式式宏定義不關心引數型別這個特點,有時候也會被利用起來,寫出一些適用性更廣的C語言程式碼,例如:
上面這段C語言宏定義程式碼實現了一個 max() 方法,它接收兩個引數,並返回較大的那個引數,max() 方法不關心引數的型別,因此 __a 和 __b 可以是 int 型的,也可以是 char 型或者 double 型以及其他資料型別的。
如果使用 max() 方法提供的功能以C語言函式的方式來寫,就稍顯麻煩些了,程式設計師不得不為每一種資料型別實現一個 max() 函式。更加糟糕的是,C語言並不支援函式的過載,因此 max() 這個函式名一旦被使用,其他函式就不能再使用了,因此相關的C語言程式碼可能是下面這樣的:
使用C語言中宏定義的注意事項C語言中的“函式式宏定義”雖然使用起來很像函式,但它實際上並不是函式,讀者千萬不能忽視這一點,不然可能會寫出具有隱患,甚至嚴重錯誤的C語言程式。請看下面這個例子:
上面這段C語言程式碼編譯並執行,會輸出什麼呢?
在 main() 函式中,變數 a 和 b 都被初始化為 2。接著呼叫了 max() 宏,傳遞的引數分別是 ++a 和 b,粗略來看,此時執行 max(++a, b),就相當於執行 max(3, 2),那上面這段C語言程式會輸出 3, 2, 3 了?得到答案最簡單粗暴的方法就是編譯並執行這段程式碼,請看:
沒有經驗的讀者看到實際輸出估計會大吃一驚,a 和 m 怎麼不是 3 而是 4 呢?並沒有第二處給 a 再加一啊?上一節曾討論,編譯器會將C語言中的宏定義展開到被呼叫處,而不是像函式那樣編譯後,再透過 call 指令呼叫。使用 gcc -E 命令檢視編譯器將上述C語言程式碼預處理後的程式碼,得到如下結果,請看:
顯然,這裡就是C語言中“函式式宏定義”的注意事項了,傳遞給 max() 的引數 ++a 會被展開到宏定義中所有的 __a 處,這就解釋了為何 a 和 m 最後都等於 4 而不是 3 了。
“函式式宏定義”還有其他與真正函式不同的地方,例如“函式式宏定義”就不適合用於遞迴等。
使用 do{}while(0)包裹程式碼儘管C語言中的“函式式宏定義”和真正的函式相比有一些缺點,但只要小心使用還是會顯著提高程式碼的執行效率的,畢竟省去了分配和釋放棧幀、傳參、傳返回值等一系列工作。正因為如此,Linux 核心中有相當多的方法是使用 define 宏定義實現的,並且,在核心C語言程式碼中,“函式式宏定義”經常藉助 do{}while(0) 實現,例如:
為什麼要用 do{}while(0) 包裹C語言程式碼呢?不使用 do{}while(0) 包裹起來有什麼不好嗎?請看下面這幾行C語言程式碼:
宏定義被編譯器展開後,會產生下面這樣的C語言程式碼:
這可能就與程式設計師的意圖不一致了,這種情況下__release(lock); 並沒有在 if(cond) 的作用範圍內。可能讀者會說,那像函式一樣,使用 {} 包裹程式碼不就可以了嗎?請再來看看下面這幾行C語言程式碼:
問題就出在 spin_unlock(lock); 後面的這個分號“;”,如果不寫就不像函式呼叫,如果寫了就會引發語法錯誤——if 語句會被這個“;”提前結束,else 無法與其配對。這麼看來,在C語言的“函式式宏定義”中使用 do{}while(0) 包裹C語言程式碼顯然就是一個不錯的方法了。
小結“函式式宏定義”並不是真正的函式,它與真正的函式是有區別的,如果弄不清楚這一點,很容易迷惑。在最後,我們一起分析了常用 do{}while(0) 包裹宏定義的程式碼的原因,讀者今後在C語言程式開發中,也可以使用該技巧。
-
3 # 問道三思
1.保證被執行一次,不會被編譯器最佳化掉。
2.避免namespace不會衝突,
3.可以在裡面設定斷點,方便除錯
-
4 # zhangyiant
因為宏只是簡單的展開,作為程式碼的預處理來出現。說白了c語言可以根本沒有宏,或者你自己定幾個關鍵字,自己在編譯前進行預處理,都可以。典型的就是qt裡面的signal, slots。標準的展開行為,有個致命的缺點,就是沒有語言的邊界處理,所以有時為了安全,用while(0)進行包裹,這是如果支援inline,那就儘量使用inline,宏的這一特性,卻有一個額外的用處,就是定義程式碼片,不是完整的程式碼段,有時你看到.....BEGIN, 和.....END的宏,就是這樣玩的,兩個必須成對的出現,看上去就像定義了新的語法一樣。
回覆列表
宏定義是一種替換型別的語句,屬於低安全性操作,所以在設計裡面宏一般用於數值替換,在不得不進行函式動態連結的時候才會使用宏函式。
在能不使用宏的情況下,儘量避免使用宏,因為宏的命令在彙編中被直接替換掉,甚至被最佳化掉,所以難以進行debug。