資料整理: jserv
考慮以下 C 程式,預期輸出結果
C99 standard (§6.7.2.1)
A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type.
在 struct {int a : 1; } obj = {.a = 1 };
的地方,原本 int
這個資料型態需要 4 bytes 的空間,即 32 個位元,我們透過 bit field 指定只用其中一個 bit 來存放數值。
但因 a 是 signed integer,依據二補數系統,1 bit 的範圍內只能表示 -1
和 0
,於是程式輸出為 not one
。
而決定 1-bit binary 1 為 -1
的是編譯器所定義,可見上述引用 C 語言規格書提及 implementation-defined 這個詞,代表該行為由實作品 (通常是指編譯器) 所決定,並對該定義有義務在文件內說明與告知。
如果題目的 int
並沒有說是 signed 或 unsigned ,那麼 bit-field 宣告之後究竟該是 signed / unsigned 是由編譯器所決定的。
延伸題目:舉出 Linux 核心中用到 bit field 的原始程式碼並解說其用途
BUILD_BUG_ON_ZERO()
在 Linux 核心原始程式碼裡頭,有個標頭檔 linux/build_bug.h 內部實作是:
該巨集用來檢查是否會有編譯錯誤,e 是個判斷式,當它經由編譯器判定為 true ,則可在編譯時期,及早由編譯器告知開發者錯誤。(因為宣告發生在編譯時期)
然後 int:(-!!(e))
是代表什麼意思?
NOT
,確保結果一定是 0 或 1-1
,因此結果會是 0
或 -1
因此,當 e 這個判斷式不合法時,即 !!(e) = 1
也就是 -!!(e) = -1
,代表宣告一個 structure 中有一個 int
,其中 32 bits 中有 -1 bit 的 bit field。反之當 e 合法時,則會宣告 int
有 0 bit 的 bit field。
-1
的 bit field 很明顯是非法的 (constant expression must be non-negative integer),那麼 0 bit 的 bit field 呢?
首先 zero-width bit field 有個規定是必須 unnamed ,再來 zero-width bit field 宣告不會使用到任何空間,但是會強制結構體下一個 bit field 對齊到下個單元的 boundary,依據 C99 標準 (§6.7.2.1):
舉例來說 (程式碼來自 What is zero-width bit field):
預期輸出是:
即 a = 111 (decimal -1), b = 11 (decimal -1), c = 1100 (decimal -4), d = 000 (decimal 0),沒有 alignment 則是 a = 111, b = 1 1, c = 1 11 1, d = 111。
但實際每次執行,d 的數值都不一樣。
重新思考過 padding 的方式,可發現上述程式碼的漏洞。zero-width bit-field 對齊的示意圖如下:
依據 C11 規格,以下三種情形會使得結構體裡頭的成員被置於不同的記憶體空間:
而這裡因為宣告一個 int 的 zero-width bit-field,依據上述提及的規範, c
,d
會被放在和 a
和 b
不同記憶體區域中,無論 c
和 d
的資料型別與資料儲存空間大小,因此 c
和 d
必定指向和 a
及 b
不同的記憶體區域 (不一定在 0xFFFF
範圍),導致 c
, d
的答案不如預期。
依據 C11 標準 (§3.14.3):
A bit-field and an adjacent non-bit-field member are in separate memory locations. The same applies to two bit-fields, if one is declared inside a nested structure declaration and the other is not, or if the two are separated by a zero-length bit-field declaration, or if they are separated by a non-bit-field member declaration. It is not safe to concurrently update two non-atomic bit-fields in the same structure if all members declared between them are also (non-zero-length) bit-fields, no matter what the sizes of those intervening bit-fields happen to be.
如果使用 gcc 的話,c 有可能會一直維持 -4
或0
不變,但用其他編譯器可能得到不同的結果,只有 a 跟 b 才是確定的。
若採用 clang 編譯, c
可能產生不同於 0
的數值,且每次執行亦可能產生不同結果
在 C11 中,提供 _Static_assert
這樣的敘述來達到上述 BUILD_BUG_ON_ZERO
的效果,就不需要繞這麼一大圈,不過 Linux 核心考量到編譯器對語言規格的支援程度,最低期待是 C99 規格。