回覆列表
  • 1 # IT劉小虎

    談到較大的C語言專案,就不得不提“宏定義”了,較大的專案都會用大量的宏定義來組織程式碼,隨便找一個開源專案,開啟它的原始碼標頭檔案,看看能發現多少宏定義。

    題主可能用過 #define N 20 這種宏定義,看起來宏定義只不過是做個替換而已,其實裡面有比較複雜的規則,有些規則可以成為實際的C語言程式開發中不錯的技巧。

    函式式宏定義

    C語言程式中像 #define N 20 這種宏定義稱為“變數式”宏定義,N 可以像變數一樣使用,但是 N 屬於常量表達式。實際上,還有一種可以像函式一樣使用的宏定義,可稱之為“函式式宏定義”,請看如下程式碼:

    將 x = MIN(3&0x0f, 5&0x0f) 表示式展開,得:

    可以看出,C語言程式中的函式式宏定義 MIN 可以像函式一樣使用,兩個實參被替換到宏定義形參 a 和 b 的位置了。應當注意,函式式宏定義和真正的函式是有區別的:

    函式式宏定義的引數沒有型別,預處理時不做引數型別檢查,所以使用時要確保型別正確。函式式宏定義本身不會被編譯為函式,呼叫時就是直接把宏定義替換過來,而不是簡單的幾條傳參和 call 指令,所以函式式宏定義編譯生成的目標會比真正的函式大。定義函式式宏定義要非常小心,如果 MIN 定義成 #define MIN(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語言程式碼展開到呼叫位置,所以它可以打印出 test 中的位置,也可以打印出 main 中的位置。如果 location 是一個真正的函式,輸出結果就不同了,請看:

    原因相信題主自己能夠分析出來,其實這就是C語言程式中,函式式宏定義的特殊之處。另外,因為呼叫函式式宏定義相當於把C語言程式碼展開,少了函式呼叫的開銷,整個C語言程式的效率也會有所提升。

  • 中秋節和大豐收的關聯?
  • 女方因為男方有乙肝選擇了離婚(沒有小孩),男方該怎麼挽回女方?還是該放棄?