謝邀。
C語(yǔ)言中的define宏定義可以像函數(shù)那樣接收參數(shù)(這種宏定義常被稱作“函數(shù)式宏定義”),不過不能像函數(shù)那樣提供參數(shù)的類型檢查,這個(gè)特點(diǎn)在有些程序員看來是不安全的。
C語(yǔ)言中的“函數(shù)式宏定義”
但是,函數(shù)式宏定義不關(guān)心參數(shù)類型這個(gè)特點(diǎn),有時(shí)候也會(huì)被利用起來,寫出一些適用性更廣的C語(yǔ)言代碼,例如:
上面這段C語(yǔ)言宏定義代碼實(shí)現(xiàn)了一個(gè)max()方法,它接收兩個(gè)參數(shù),并返回較大的那個(gè)參數(shù),max()方法不關(guān)心參數(shù)的類型,因此__a和__b可以是int型的,也可以是char型或者double型以及其他數(shù)據(jù)類型的。
如果使用max()方法提供的功能以C語(yǔ)言函數(shù)的方式來寫,就稍顯麻煩些了,程序員不得不為每一種數(shù)據(jù)類型實(shí)現(xiàn)一個(gè)max()函數(shù)。更加糟糕的是,C語(yǔ)言并不支持函數(shù)的重載,因此max()這個(gè)函數(shù)名一旦被使用,其他函數(shù)就不能再使用了,因此相關(guān)的C語(yǔ)言代碼可能是下面這樣的:
這樣對(duì)比起來,顯然使用define宏來定義max()方法更加方便一些。不過,C語(yǔ)言中的宏定義不提供參數(shù)類型檢查的確也是一個(gè)缺點(diǎn),它可能會(huì)導(dǎo)致程序的不安全,讀者不應(yīng)忽視這一點(diǎn)。因此如果不是必須要使用define宏定義才能解決問題,應(yīng)該盡可能的使用函數(shù),若是希望能夠得到較高效率的代碼,可以使用inline函數(shù)。
關(guān)于inline函數(shù),我之前的文章較為詳細(xì)的討論過。
使用C語(yǔ)言中宏定義的注意事項(xiàng)
C語(yǔ)言中的“函數(shù)式宏定義”雖然使用起來很像函數(shù),但它實(shí)際上并不是函數(shù),讀者千萬(wàn)不能忽視這一點(diǎn),不然可能會(huì)寫出具有隱患,甚至嚴(yán)重錯(cuò)誤的C語(yǔ)言程序。請(qǐng)看下面這個(gè)例子:
上面這段C語(yǔ)言代碼編譯并執(zhí)行,會(huì)輸出什么呢?
在main()函數(shù)中,變量a和b都被初始化為2。接著調(diào)用了max()宏,傳遞的參數(shù)分別是++a和b,粗略來看,此時(shí)執(zhí)行max(++a,b),就相當(dāng)于執(zhí)行max(3,2),那上面這段C語(yǔ)言程序會(huì)輸出3,2,3了?得到答案最簡(jiǎn)單粗暴的方法就是編譯并執(zhí)行這段代碼,請(qǐng)看:
沒有經(jīng)驗(yàn)的讀者看到實(shí)際輸出估計(jì)會(huì)大吃一驚,a和m怎么不是3而是4呢?并沒有第二處給a再加一啊?上一節(jié)曾討論,編譯器會(huì)將C語(yǔ)言中的宏定義展開到被調(diào)用處,而不是像函數(shù)那樣編譯后,再通過call指令調(diào)用。使用gcc-E命令查看編譯器將上述C語(yǔ)言代碼預(yù)處理后的代碼,得到如下結(jié)果,請(qǐng)看:
顯然,這里就是C語(yǔ)言中“函數(shù)式宏定義”的注意事項(xiàng)了,傳遞給max()的參數(shù)++a會(huì)被展開到宏定義中所有的__a處,這就解釋了為何a和m最后都等于4而不是3了。
“函數(shù)式宏定義”還有其他與真正函數(shù)不同的地方,例如“函數(shù)式宏定義”就不適合用于遞歸等。
使用do{}while(0)包裹代碼
盡管C語(yǔ)言中的“函數(shù)式宏定義”和真正的函數(shù)相比有一些缺點(diǎn),但只要小心使用還是會(huì)顯著提高代碼的執(zhí)行效率的,畢竟省去了分配和釋放棧幀、傳參、傳返回值等一系列工作。正因?yàn)槿绱耍琇inux內(nèi)核中有相當(dāng)多的方法是使用define宏定義實(shí)現(xiàn)的,并且,在內(nèi)核C語(yǔ)言代碼中,“函數(shù)式宏定義”經(jīng)常借助do{}while(0)實(shí)現(xiàn),例如:
為什么要用do{}while(0)包裹C語(yǔ)言代碼呢?不使用do{}while(0)包裹起來有什么不好嗎?請(qǐng)看下面這幾行C語(yǔ)言代碼:
宏定義被編譯器展開后,會(huì)產(chǎn)生下面這樣的C語(yǔ)言代碼:
這可能就與程序員的意圖不一致了,這種情況下__release(lock);并沒有在if(cond)的作用范圍內(nèi)。可能讀者會(huì)說,那像函數(shù)一樣,使用{}包裹代碼不就可以了嗎?請(qǐng)?jiān)賮砜纯聪旅孢@幾行C語(yǔ)言代碼:
問題就出在spin_unlock(lock);后面的這個(gè)分號(hào)“;”,如果不寫就不像函數(shù)調(diào)用,如果寫了就會(huì)引發(fā)語(yǔ)法錯(cuò)誤——if語(yǔ)句會(huì)被這個(gè)“;”提前結(jié)束,else無(wú)法與其配對(duì)。這么看來,在C語(yǔ)言的“函數(shù)式宏定義”中使用do{}while(0)包裹C語(yǔ)言代碼顯然就是一個(gè)不錯(cuò)的方法了。
小結(jié)
“函數(shù)式宏定義”并不是真正的函數(shù),它與真正的函數(shù)是有區(qū)別的,如果弄不清楚這一點(diǎn),很容易迷惑。在最后,我們一起分析了常用do{}while(0)包裹宏定義的代碼的原因,讀者今后在C語(yǔ)言程序開發(fā)中,也可以使用該技巧。
歡迎在評(píng)論區(qū)一起討論,質(zhì)疑。文章都是手打原創(chuàng),每天最淺顯的介紹C語(yǔ)言、linux等嵌入式開發(fā),喜歡我的文章就關(guān)注一波吧,可以看到最新更新和之前的文章哦。