我碰到了/usr/include/linux/kernel.h中的這個奇怪的宏代碼: html
/* Force a compilation error if condition is true, but also produce a result (of value 0 and type size_t), so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted). */ #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) #define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
有什麼用:-!!
作? linux
好吧,我很驚訝沒有提到該語法的替代方法。 另外一種常見的(但較舊的)機制是調用未定義的函數,若是斷言正確,則依靠優化器來編譯出該函數調用。 git
#define MY_COMPILETIME_ASSERT(test) \ do { \ extern void you_did_something_bad(void); \ if (!(test)) \ you_did_something_bad(void); \ } while (0)
雖然此機制有效(只要啓用了優化),但它的缺點是直到您連接後才報告錯誤,這時它將沒法找到函數you_did_something_bad()的定義。 這就是內核開發人員開始使用諸如負數大小的位域寬度和負數大小的數組之類的技巧的緣由(後者後來中止了破壞GCC 4.4中的構建)。 express
出於對編譯時斷言的需求的同情,GCC 4.3引入了error
函數屬性 ,該屬性使您能夠擴展這個較舊的概念,但會生成編譯時錯誤,並帶有您選擇的消息-再也不用神祕的「負數大小」陣列」錯誤消息! 數組
#define MAKE_SURE_THIS_IS_FIVE(number) \ do { \ extern void this_isnt_five(void) __attribute__((error( \ "I asked for five and you gave me " #number))); \ if ((number) != 5) \ this_isnt_five(); \ } while (0)
事實上,因爲Linux 3.9的,咱們如今有一個宏叫compiletime_assert
使用這種功能和最in的宏bug.h
已進行相應更新。 儘管如此,該宏仍不能用做初始化程序。 可是,使用by 語句表達式 (另外一個GCC C擴展名),您能夠! 函數
#define ANY_NUMBER_BUT_FIVE(number) \ ({ \ typeof(number) n = (number); \ extern void this_number_is_five(void) __attribute__(( \ error("I told you not to give me a five!"))); \ if (n == 5) \ this_number_is_five(); \ n; \ })
該宏將只對它的參數進行一次評估(以防它產生反作用),併產生一個編譯時錯誤,提示「我告訴過你不要給我五個!」 若是表達式的計算結果爲5或不是編譯時常數。 測試
那麼,爲何不使用這個而不是負大小的位域呢? 遺憾的是,當前使用語句表達式有不少限制,包括將它們用做常量初始化程序(用於枚舉常量,位域寬度等),即便語句表達式自己是徹底恆定的(便可以徹底求值)在編譯時,不然經過__builtin_constant_p()
測試)。 此外,它們不能在功能主體以外使用。 優化
但願GCC會盡快修正這些缺點,並容許將常量語句表達式用做常量初始化程序。 這裏的挑戰是定義什麼是合法常量表達式的語言規範。 C ++ 11僅爲此類型或事物添加了constexpr關鍵字,但C11中沒有對應的關鍵字。 儘管C11確實得到了靜態斷言,這將解決部分問題,但它沒法解決全部這些缺點。 所以,我但願gcc能夠經過-std = gnuc99&-std = gnuc11或相似的方式使constexpr功能做爲擴展使用,並容許其在語句表達式等上使用。 等 ui
:
是位域。 至於!!
,這是邏輯上的雙重否認 ,所以返回0
表示false或1
表示true。 -
是減號,即算術求反。 this
這只是使編譯器對無效輸入bar之以鼻的一種技巧。
考慮BUILD_BUG_ON_ZERO
。 當-!!(e)
計算爲負值時,將產生編譯錯誤。 不然, -!!(e)
值爲0,而且寬度爲0的位字段的大小爲0。所以,宏的值的值爲size_t
,值爲0。
在我看來,這個名稱很薄弱,由於當輸入不爲零時,構建實際上會失敗。
BUILD_BUG_ON_NULL
很是類似,可是產生一個指針而不是一個int
。
若是條件爲假,它將建立大小爲0
位域,但若是條件爲真/非零,則建立大小爲-1
( -!!1
)的位域。 在前一種狀況下,沒有錯誤,而且使用int成員初始化了該結構。 在後一種狀況下,會出現編譯錯誤(固然不會建立大小爲-1
位域)。
實際上,這是一種檢查表達式e是否能夠評估爲0的方法,若是不能,則使build失敗 。
該宏的名稱有些錯誤; 它應該更像BUILD_BUG_OR_ZERO
,而不是...ON_ZERO
。 ( 偶爾會討論這個名稱是否使人困惑 。)
您應該這樣閱讀表達式:
sizeof(struct { int: -!!(e); }))
(e)
:計算表達式e
。
!!(e)
在邏輯否認兩次: 0
若是e == 0
; 不然1
。
-!!(e)
在數字上否認表達來自步驟2: 0
,若是它是0
; 不然爲-1
。
struct{int: -!!(0);} --> struct{int: 0;}
:若是它爲零,則咱們聲明一個結構,該結構具備一個寬度爲零的匿名整數位字段。 一切都很好,咱們會照常進行。
struct{int: -!!(1);} --> struct{int: -1;}
:另外一方面,若是它不爲零,則它將爲負數。 聲明任何寬度爲負的位字段都是編譯錯誤。
所以,咱們要麼在結構中使用寬度爲0的位域(這很好),要麼使用寬度爲負的位域(這是編譯錯誤)結束。 而後,咱們使用該字段的sizeof
,從而得到具備適當寬度的size_t
(在e
爲零的狀況下爲零)。
有人問: 爲何不僅使用assert
?
keithmo的回答在這裏獲得了很好的迴應:
這些宏實現編譯時測試,而assert()是運行時測試。
很是正確。 您不想在運行時檢測內核中可能早已發現的問題! 這是操做系統的關鍵部分。 不管在何種程度上能夠在編譯時檢測到問題,都更好。
有些人彷佛將這些宏與assert()
混淆了。
這些宏實現編譯時測試,而assert()
是運行時測試。