2012年10月14日 星期日

Linux Kernel: BUILD_BUG_ON_ZERO() / BUILD_BUG_ON_NULL()

之前在trace Linux Kernel source codes時發現了兩個很特別的macro:BUILD_BUG_ON_ZERO() 和 BUILD_BUG_ON_NULL()
(定義在:include/linux/kernel.h)
它們的定義如下:


其中e是我們所傳入的判斷式,若判斷式為true,則會造成compile error
如此我們便可透過這個macro來判斷是否某些錯誤/不應發生的情況(判斷式)是否會發生
若會發生則可在compile-time的時候就顯示錯誤訊息

一開始看不太懂這個macro的意義
上網查了資料後發現在stackoverflow上有人做了很詳細的解釋:What is 「:-!!」 in C code?


如同stackoverflow上所解釋,這段codes可以拆成下面幾個片段來分析:
  • !!(e)
將所傳入的e做兩次negative,如此可以確保只要e不為0結果一定為1,e為0結果仍為0
  • -!!(e)
將剛剛的結果乘上 (-1),因此只要e不為0結果就會是-1,e為0結果仍為0
  • struct { int:-1!!(e) }
宣告一個structure,包含一個int,這邊用到了C語言Bit-fields的技巧
根據維基百科Bit-fields的定義:
A bit field is a common idiom used in computer programming to compactly store multiple logical values as a short series of bits where each of the single bits can be addressed separately.
也就是說我們可以將資料以bit的形式儲存在某一個資料型態中
舉例來說:

就宣告了三個bit-fields:a, b, c
這三個bit-fields會包在同一個unsigned char資料型態(8-bits)中
其中a佔了1個bit,b佔了3個bits,c佔了2個bits
但整個flags structure還是會佔8個bits (1 byte),即使bit-fields並沒有佔滿整個8 bits空間
此外,bit-fileds是無法使用sizeof()取得其size的
因此以下的codes將會產生:error: 'sizeof' applied to a bit-field 的錯誤訊息

回到原本的struct { int:-1!!(e) },我們可以得知:
e不為0,則struct { int:-!!(e); } 會展開成:struct { int:-1; },也就是會宣告一個structure,包含一個佔int (32 bits)中,-1個bits的anonymous bit-field
當然,絕對不會有佔-1個bits的bit-field存在
因此這樣會導致在編譯時產生:error: negative width in bit-field '<anonymous>' 的錯誤訊息
e為0,則struct { int:-1!!(e); } 會展開成:struct { int:0; },也就是宣告一個structure,包含一個佔int (32 bits)中,0個bits的anonymous bit-field
0個bits的bit-fields並不會造成編譯出錯,事實上,宣告成0個bits的bit-files通常是用來將資料強制對齊至下一個word邊界 (Force alignment at the next word boundary),而且不會佔任何的空間!! 
透過這樣的方式,只要傳入BUILD_BUG_ON_ZERO(e)e不為0,其就會造成編譯出錯
若傳入BUILD_BUG_ON_ZERO(e)e為0,則只會宣告一個不佔任何空間的structure,經過sizeof()計算後回傳0的值

同樣的BUILD_BUG_ON_NULL()則是將上述的結果轉成void *
因此只要傳入BUILD_BUG_ON_NULL(e)e不為NULL,其就會造成編譯出錯
若傳入BUILD_BUG_ON_NULL(e)e為NULL,則只會宣告一個不佔任何空間的structure
經過sizeof()計算後回傳0,再轉成一個指向位址0的void *

而stackoverflow上的回答也有提到,為何不直接使用assert()就好了?
其答案也很清楚:These macros implement a compile-time test, while assert() is a run-time test
也就是說這樣的機制是可以在compile-time的時候就發現問題
assert()則必須等到run-time的時候才能發現問題

不過也就是因為BUILD_BUG_ON_ZERO() 和 BUILD_BUG_ON_NULL() 只能使用在compile-time就可以找到bug的情況下
因此若是判斷式中有任何必須等到run-time才能得知的結果,就會造成錯誤
如下面的程式:


第一次呼叫BUILD_BUG_ON_ZERO() 由於傳入的判斷式中包含run-time才會得知其值的a
因此會造成compiler錯誤的判斷,產生錯誤訊息:error: bit-field '<anonymous>' width not an integer constant

但第二次呼叫BUILD_BUG_ON_ZERO() 由於判斷式展開後皆可在compile-time的時候得知其結果
因此就不會產生錯誤訊息

所以在使用BUILD_BUG_ON_ZERO() 或是 BUILD_BUG_ON_NULL() 的時候還是要注意其使用時機.....

不得不說Linux內用了許多非常漂亮的技巧...
不但可以在compile-time的時候就將錯誤顯示出來
若判斷式(錯誤/不應發生的情況)不成立亦不會造成任何空間的浪費!!

BUILD_BUG_ON_ZERO() 的實際用法可以參考下一篇文章

沒有留言: