謝邀。
談到較大的C語言項目,就不得不提“宏定義”了,較大的項目都會用大量的宏定義來組織代碼,隨便找一個開源項目,打開它的源代碼頭文件,看看能發現多少宏定義。
題主可能用過#defineN20這種宏定義,看起來宏定義只不過是做個替換而已,其實里面有比較復雜的規則,有些規則可以成為實際的C語言程序開發中不錯的技巧。
函數式宏定義
C語言程序中像#defineN20這種宏定義稱為“變量式”宏定義,N可以像變量一樣使用,但是N屬于常量表達式。實際上,還有一種可以像函數一樣使用的宏定義,可稱之為“函數式宏定義”,請看如下代碼:
將x=MIN(3&0x0f,5&0x0f)表達式展開,得:
可以看出,C語言程序中的函數式宏定義MIN可以像函數一樣使用,兩個實參被替換到宏定義形參a和b的位置了。應當注意,函數式宏定義和真正的函數是有區別的:
- 函數式宏定義的參數沒有類型,預處理時不做參數類型檢查,所以使用時要確保類型正確。
- 函數式宏定義本身不會被編譯為函數,調用時就是直接把宏定義替換過來,而不是簡單的幾條傳參和call指令,所以函數式宏定義編譯生成的目標會比真正的函數大。
- 定義函數式宏定義要非常小心,如果MIN定義成#defineMIN(a,b)(a<b?a:b),則x=MIN(3&0x0f,5&0x0f)展開就成了x=(3&0x0f<5&0x0f?3&0x0f:5&0x0f),運算符的優先級就錯了,不會得出正確結果。讀者思考一下,外層括號能否省略?
- 因為調用函數式宏定義就是簡單替換,所以如果MIN(i++,j++)時,展開就是((i++)<(j++)?(i++):(j++)),i和j自加的次數是不確定的。如果是MIN真正的函數,則i和j確定是只自加一次。
宏定義的小技巧和注意事項
在Linux內核中,函數式宏定義通常使用do{…}while(0)包裹,請看下面的C語言代碼示例:
為什么呢?請看下面這段C語言代碼,就明白了:
如果沒有使用do{…}while(0)包裹,把do_something展開后,變為:
printf(“i=%d\n”,i);這句沒有被包含在if判斷語句里,而且else語句并沒有與if配對,所以編譯會報錯。那能否在宏定義時,使用{}包裹呢?還是上面的例子,使用{}包裹展開后:
雖然printf(“i=%d\n”,i);這句被包含在if判斷語句里了,但是do_something(i);最后的“;”會被展開到{}后面,這樣表示if的判斷結束了,else依然沒有與if配對,還是會編譯報錯。
那do_something(i);后面的這個“;”不寫不就行了嗎?的確,不寫就沒有錯誤了,但是不寫“;”,看起來就不像函數調用了,對不?整個語句顯得怪怪的,哪天順手一加,就又錯了。
有時候,C語言函數式宏定義可以做到函數難以實現的事
現在的C語言及其編譯器支持了很多有趣的關鍵字,例如:
請看如下C語言代碼:
編譯器在編譯時,會自動的把“__FUNTION__”和“__LINE__”替換為函數名和行號,這樣就不用程序員逐個手動輸入了,而且C語言代碼的可移植性也更強。
為了更方便的輸出當前位置,我們可以定義函數式宏定義:
打印出C語言語句位置是有用的,它能幫助我們在大型項目的復雜代碼中快速的找到出錯的函數,出錯的行號。(類似于__LINE__的關鍵字還有一些,留給題主自行查閱了)
location是一個
原因相信題主自己能夠分析出來,其實這就是C語言程序中,函數式宏定義的特殊之處。另外,因為調用函數式宏定義相當于把C語言代碼展開,少了函數調用的開銷,整個C語言程序的效率也會有所提升。
歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。