開門見山
在編寫程序時,我們總是希望能夠盡早地發(fā)現(xiàn)問題,而在編碼的不同階段,發(fā)現(xiàn)問題的手段也因時而異。
對C語言開發(fā)的項目來說,BUILD_BUG_ON
就是這么一個可以在編譯期間檢查代碼靜態(tài)約束的宏。
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
BUILD_BUG_ON
的原理如下:
當(dāng)
condition
為false(0)時,由于1-2*!!(condition)
為 1,宏被展開為sizeof(char[1])
,這是合法語句,并且編譯后不產(chǎn)生額外代碼;當(dāng)
condition
為true(非0)時,由于1-2*!!(condition)
為 -1,宏被展開為sizeof(char[-1])
,而這是非法語句,編譯報錯。
condition
代表的是非預(yù)期的情況,如果這種非預(yù)期的情況真的出現(xiàn)了,那么問題將在編譯期就被發(fā)現(xiàn)。
語法解析
細(xì)心的朋友可能會問:sizeof(char[1])
這條語句是什么意思呢?
這個表達(dá)式中,char[1]
其實是一個類型,sizeof(char[1])
就是該類型(即長度為1的char型數(shù)組)所占內(nèi)存大小。
欸?char[1]
居然是類型,那C語言是不是可以用這種方式定義數(shù)組呢:
char[2] arr = { 1, 2 };
答案是:不可以,這不符合C語言的語法。正確寫法是我們熟悉的下面這種:
char arr[2] = { 1, 2 };
事實上,如果要用typedef定義一個長度為2的char型數(shù)組類型,應(yīng)該這么做:
// 錯誤寫法:
typedef char[2] char_2;
// 正確寫法:
typedef char char_2[2];
char_2 arr = { 1, 2 };
這是由于C語言并不是一個完全的前綴類型聲明語言,有些類型如果要做到前綴類型聲明,需要用typedef這個關(guān)鍵字來為類型定義一個新名字。比如函數(shù)類型:
// 定義函數(shù)
void test_func(int a, char b) { … }
// 不使用typedef
void (*func)(int, char) = test_func;
// 使用typedef
typedef void (*func_t)(int, char);
func_t func = test_func;
同理,sizeof運算符也可以計算其占用內(nèi)存大?。?/p>
// 以下2種寫法是等價的,64位系統(tǒng)中結(jié)果為8
sizeof(void (*)(int, char))
sizeof(func_t)
應(yīng)用場景
BUILD_BUG_ON
常用于判斷編譯時確定,且無法在預(yù)編譯時確定的條件表達(dá)式,使用時將其放在需要判斷條件約束的相關(guān)函數(shù)內(nèi)即可。
使用的場景例如:結(jié)構(gòu)體類型的大小是否滿足某種條件,或者兩個枚舉常量的映射關(guān)系是否符合要求。
拓展
除了BUILD_BUG_ON
,還有一些實用的宏同樣能在編譯期檢查代碼的靜態(tài)約束:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
原理如下:
這2個宏利用了C語言結(jié)構(gòu)體位字段長度不能為負(fù)數(shù)的性質(zhì),分別實現(xiàn)宏參數(shù)非0報錯(預(yù)期為0)和非
NULL
報錯(預(yù)期為NULL
)。
在linux 3.19內(nèi)核/include/linux/bug.h
中,還有個BUILD_BUG_ON
宏的升級版BUILD_BUG_ON_MSG(cond, msg)
,這個宏額外多了一個msg參數(shù),用來設(shè)定不滿足條件時編譯錯誤所打印的信息,不過該宏是通過gcc的拓展特性實現(xiàn),并不通用。