'雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼'

C語言 程序員 Linux 文章 嵌入式時代 2019-08-16
"

今天翻看 Linux 內核源代碼時,發現兩行非常有意思的C語言代碼,如下:

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
"

今天翻看 Linux 內核源代碼時,發現兩行非常有意思的C語言代碼,如下:

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

有意思的宏

這兩行C語言代碼有什麼含義呢?

要理解這兩行C語言代碼,關鍵就是理解 int:-!!(e) ,但是“:-!!”符號看起來很陌生,C語言中似乎並沒有這樣的符號。其實不是的,“:-!!”這幾個符號都是C語言中的基本符號組成的。

首先,不應該將“:”與 int 剝離,所以 int:-!!(e) 應該這麼看,int: (-!!(e)),這就清楚了,顯然是位域(bitfield)的定義方法,其中 -!!(e) 是位域的長度。

對於 -!!(e),應該將 e 看作是一個條件表達式,此時 !! 符號可以將其轉換為布爾值(即0或者1,讀者自己思考原因)。在C語言中,非零即可認為是真,因此 2,3,88 等都看看作真。在本例中,定義位域時,長度不應該超過 int 的寬度,所以如果沒有 !! 符號,BUILD_BUG_ON_XX 宏的適用範圍就很小了。

"

今天翻看 Linux 內核源代碼時,發現兩行非常有意思的C語言代碼,如下:

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

有意思的宏

這兩行C語言代碼有什麼含義呢?

要理解這兩行C語言代碼,關鍵就是理解 int:-!!(e) ,但是“:-!!”符號看起來很陌生,C語言中似乎並沒有這樣的符號。其實不是的,“:-!!”這幾個符號都是C語言中的基本符號組成的。

首先,不應該將“:”與 int 剝離,所以 int:-!!(e) 應該這麼看,int: (-!!(e)),這就清楚了,顯然是位域(bitfield)的定義方法,其中 -!!(e) 是位域的長度。

對於 -!!(e),應該將 e 看作是一個條件表達式,此時 !! 符號可以將其轉換為布爾值(即0或者1,讀者自己思考原因)。在C語言中,非零即可認為是真,因此 2,3,88 等都看看作真。在本例中,定義位域時,長度不應該超過 int 的寬度,所以如果沒有 !! 符號,BUILD_BUG_ON_XX 宏的適用範圍就很小了。

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

現在明白了

現在明白了,!!(e) 的值要麼是 0,要麼是 1。再考慮前面的負號,-!!(e) 要麼是 0,要麼是 -1,即對於 int:-!!(e) 來說,只有兩種情況:

int: 0
// 或者
int: -1

顯然,位域的長度不能是負數,所以如果表達式 e 為真時,宏 BUILD_BUG_ON_XX 就是非法的了,在編譯階段就會報錯

“編譯時”和“運行時”

從某種程度上來看,上述C語言宏可以看作是編譯時的 assert()。有讀者可能會問,既然如此,為什麼不直接使用 assert(),而是花大力氣自定義呢?

"

今天翻看 Linux 內核源代碼時,發現兩行非常有意思的C語言代碼,如下:

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

有意思的宏

這兩行C語言代碼有什麼含義呢?

要理解這兩行C語言代碼,關鍵就是理解 int:-!!(e) ,但是“:-!!”符號看起來很陌生,C語言中似乎並沒有這樣的符號。其實不是的,“:-!!”這幾個符號都是C語言中的基本符號組成的。

首先,不應該將“:”與 int 剝離,所以 int:-!!(e) 應該這麼看,int: (-!!(e)),這就清楚了,顯然是位域(bitfield)的定義方法,其中 -!!(e) 是位域的長度。

對於 -!!(e),應該將 e 看作是一個條件表達式,此時 !! 符號可以將其轉換為布爾值(即0或者1,讀者自己思考原因)。在C語言中,非零即可認為是真,因此 2,3,88 等都看看作真。在本例中,定義位域時,長度不應該超過 int 的寬度,所以如果沒有 !! 符號,BUILD_BUG_ON_XX 宏的適用範圍就很小了。

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

現在明白了

現在明白了,!!(e) 的值要麼是 0,要麼是 1。再考慮前面的負號,-!!(e) 要麼是 0,要麼是 -1,即對於 int:-!!(e) 來說,只有兩種情況:

int: 0
// 或者
int: -1

顯然,位域的長度不能是負數,所以如果表達式 e 為真時,宏 BUILD_BUG_ON_XX 就是非法的了,在編譯階段就會報錯

“編譯時”和“運行時”

從某種程度上來看,上述C語言宏可以看作是編譯時的 assert()。有讀者可能會問,既然如此,為什麼不直接使用 assert(),而是花大力氣自定義呢?

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

為什麼不直接使用 assert(),而是花大力氣自定義呢?

讀者應該注意“編譯時”這個關鍵詞,BUILD_BUG_ON_XX 宏在編譯階段就可以檢查錯誤,這就能確保程序員能夠在程序運行之前發現錯誤,並修改相關的C語言代碼。與之對應的, assert() 只能在程序運行時檢查錯誤,程序運行時出錯就麻煩了,至少需要程序員編寫相應的錯誤處理邏輯C語言代碼。

如果能夠在程序開發階段發現錯誤,是多麼美好的一件事啊。
"

今天翻看 Linux 內核源代碼時,發現兩行非常有意思的C語言代碼,如下:

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

有意思的宏

這兩行C語言代碼有什麼含義呢?

要理解這兩行C語言代碼,關鍵就是理解 int:-!!(e) ,但是“:-!!”符號看起來很陌生,C語言中似乎並沒有這樣的符號。其實不是的,“:-!!”這幾個符號都是C語言中的基本符號組成的。

首先,不應該將“:”與 int 剝離,所以 int:-!!(e) 應該這麼看,int: (-!!(e)),這就清楚了,顯然是位域(bitfield)的定義方法,其中 -!!(e) 是位域的長度。

對於 -!!(e),應該將 e 看作是一個條件表達式,此時 !! 符號可以將其轉換為布爾值(即0或者1,讀者自己思考原因)。在C語言中,非零即可認為是真,因此 2,3,88 等都看看作真。在本例中,定義位域時,長度不應該超過 int 的寬度,所以如果沒有 !! 符號,BUILD_BUG_ON_XX 宏的適用範圍就很小了。

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

現在明白了

現在明白了,!!(e) 的值要麼是 0,要麼是 1。再考慮前面的負號,-!!(e) 要麼是 0,要麼是 -1,即對於 int:-!!(e) 來說,只有兩種情況:

int: 0
// 或者
int: -1

顯然,位域的長度不能是負數,所以如果表達式 e 為真時,宏 BUILD_BUG_ON_XX 就是非法的了,在編譯階段就會報錯

“編譯時”和“運行時”

從某種程度上來看,上述C語言宏可以看作是編譯時的 assert()。有讀者可能會問,既然如此,為什麼不直接使用 assert(),而是花大力氣自定義呢?

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

為什麼不直接使用 assert(),而是花大力氣自定義呢?

讀者應該注意“編譯時”這個關鍵詞,BUILD_BUG_ON_XX 宏在編譯階段就可以檢查錯誤,這就能確保程序員能夠在程序運行之前發現錯誤,並修改相關的C語言代碼。與之對應的, assert() 只能在程序運行時檢查錯誤,程序運行時出錯就麻煩了,至少需要程序員編寫相應的錯誤處理邏輯C語言代碼。

如果能夠在程序開發階段發現錯誤,是多麼美好的一件事啊。
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

如果能夠在程序開發階段發現錯誤,是多麼美好的一件事啊。

那 assert() 就沒有存在的必要了?暫時還不是,對於 BUILD_BUG_ON_XX 宏中的條件表達式 e,目前的C語言語法只支持常量表達式,對於變量表達式就無能為力了,只能使用 assert(),例如:

int a = 1;
BUILD_BUG_ON_ZERO(1<0); // 合法
BUILD_BUG_ON_ZERO(a<0); // 非法
assert(a<0); // 合法

讀者可能會問,BUILD_BUG_ON_XX 宏只能判斷常量表達式,那它還有什麼應用價值呢?畢竟兩個常量的對比誰會弄錯呢?BUILD_BUG_ON_XX 宏當然有應用價值,而且還挺好用,下一節將結合實例討論,敬請關注。

事實上,這種藉助C語言語法的實現編譯時判斷的技巧有很多種,例如:

"

今天翻看 Linux 內核源代碼時,發現兩行非常有意思的C語言代碼,如下:

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

有意思的宏

這兩行C語言代碼有什麼含義呢?

要理解這兩行C語言代碼,關鍵就是理解 int:-!!(e) ,但是“:-!!”符號看起來很陌生,C語言中似乎並沒有這樣的符號。其實不是的,“:-!!”這幾個符號都是C語言中的基本符號組成的。

首先,不應該將“:”與 int 剝離,所以 int:-!!(e) 應該這麼看,int: (-!!(e)),這就清楚了,顯然是位域(bitfield)的定義方法,其中 -!!(e) 是位域的長度。

對於 -!!(e),應該將 e 看作是一個條件表達式,此時 !! 符號可以將其轉換為布爾值(即0或者1,讀者自己思考原因)。在C語言中,非零即可認為是真,因此 2,3,88 等都看看作真。在本例中,定義位域時,長度不應該超過 int 的寬度,所以如果沒有 !! 符號,BUILD_BUG_ON_XX 宏的適用範圍就很小了。

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

現在明白了

現在明白了,!!(e) 的值要麼是 0,要麼是 1。再考慮前面的負號,-!!(e) 要麼是 0,要麼是 -1,即對於 int:-!!(e) 來說,只有兩種情況:

int: 0
// 或者
int: -1

顯然,位域的長度不能是負數,所以如果表達式 e 為真時,宏 BUILD_BUG_ON_XX 就是非法的了,在編譯階段就會報錯

“編譯時”和“運行時”

從某種程度上來看,上述C語言宏可以看作是編譯時的 assert()。有讀者可能會問,既然如此,為什麼不直接使用 assert(),而是花大力氣自定義呢?

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

為什麼不直接使用 assert(),而是花大力氣自定義呢?

讀者應該注意“編譯時”這個關鍵詞,BUILD_BUG_ON_XX 宏在編譯階段就可以檢查錯誤,這就能確保程序員能夠在程序運行之前發現錯誤,並修改相關的C語言代碼。與之對應的, assert() 只能在程序運行時檢查錯誤,程序運行時出錯就麻煩了,至少需要程序員編寫相應的錯誤處理邏輯C語言代碼。

如果能夠在程序開發階段發現錯誤,是多麼美好的一件事啊。
雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

如果能夠在程序開發階段發現錯誤,是多麼美好的一件事啊。

那 assert() 就沒有存在的必要了?暫時還不是,對於 BUILD_BUG_ON_XX 宏中的條件表達式 e,目前的C語言語法只支持常量表達式,對於變量表達式就無能為力了,只能使用 assert(),例如:

int a = 1;
BUILD_BUG_ON_ZERO(1<0); // 合法
BUILD_BUG_ON_ZERO(a<0); // 非法
assert(a<0); // 合法

讀者可能會問,BUILD_BUG_ON_XX 宏只能判斷常量表達式,那它還有什麼應用價值呢?畢竟兩個常量的對比誰會弄錯呢?BUILD_BUG_ON_XX 宏當然有應用價值,而且還挺好用,下一節將結合實例討論,敬請關注。

事實上,這種藉助C語言語法的實現編譯時判斷的技巧有很多種,例如:

雖然這兩個C語言宏定義很簡單,但是能在程序運行前找到錯誤代碼

藉助C語言語法的實現編譯時判斷的技巧

它們的原理和作用都是類似的,留給讀者自己分析了,這裡不再贅述。

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。

"

相關推薦

推薦中...