# bit-field 考慮以下 C 程式,預期輸出結果 ```cpp #include <stdbool.h> #include <stdio.h> bool is_one(int i) { return i == 1; } int main() { struct { signed int a : 1; } obj = { .a = 1 }; puts(is_one(obj.a) ? "one" : "not one"); return 0; } ``` * [C: Bit Fields](https://www.tutorialspoint.com/cprogramming/c_bit_fields.htm) :::warning **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**,依據[二補數系統](https://hackmd.io/@sysprog/binary-representation),1 bit 的範圍內只能表示 `0` ~ `-1`,於是程式輸出為 `not one`。 而決定 1-bit binary 1 為 `-1` 的是編譯器所定義,可見上述引用 C 語言規格書提及 implementation-defined 這個詞,代表該行為由實作品 (通常是指編譯器) 所決定,並對該定義有義務在文件內說明與告知。 如果題目的 `int` 並沒有說是 signed 或 unsigned ,那麼 bit-field 宣告之後究竟該是 signed / unsigned 是由編譯器所決定的。 :::success 延伸題目:舉出 Linux 核心中用到 bit field 的原始程式碼並解說其用途 ::: ### Linux 核心: BUILD_BUG_ON_ZERO() 在 Linux 核心原始程式碼裡頭,有個標頭檔 [linux/build_bug.h](https://elixir.bootlin.com/linux/latest/source/include/linux/build_bug.h#L16) 內部實作是: ```cpp /* * 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)); })) ``` 這個 macro 是用來檢查是否會有 compilation error,e 是個判斷式,當它經由這個 macro 結果為 true ,則可以在 compile-time 的時候被告知有錯。(因為宣告發生在 compile-time) 然後 `int:(-!!(e))` 是代表什麼意思? * !!(e):對 e 做兩次 `NOT`,確保結果一定是 0 或 1 * -!!(e):對 !!(e) 乘上 `-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 宣告不會使用到任何空間,但是會強制 structure 中下一個 bit field 對齊到下一個 unit 的 boundary : :::warning **C99 standard (§6.7.2.1)** * The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted. If the value is zero, the declaration shall have no declarator. * A bit-field declaration with no declarator, but only a colon and a width, indicates an unnamed bit-field. As a special case, a bit-field structure member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bit-field, if any, was placed. * (108) An unnamed bit-field structure member is useful for padding to conform to externally imposed layouts. ::: 舉例來說 (使用的程式來自[這裡](https://stackoverflow.com/questions/13802728/what-is-zero-width-bit-field)): ```cpp struct foo { int a : 3; int b : 2; int : 0; /* Force alignment to next boundary */ int c : 4; int d : 3; }; int main() { int i = 0xFFFF; struct foo *f = (struct foo *) &i; printf("a=%d\nb=%d\nc=%d\nd=%d\n", f->a, f->b, f->c, f->d); return 0; } ``` 預期輸出是: ``` a=-1 b=-1 c=-4 d=0 ``` 即 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 對齊的示意圖如下: ``` i = 1111 1111 1111 1111 padding & start from here ↓ 1111 1111 1111 1111 b baaa dddcccc |← int 32 bits →|← int 32 bits →| ``` 而這裡因為宣告一個 int 的 zero-width bit-field ,所以要對齊的長度為 32 bit,`c` 和 `d` 早已超出 `0xFFFF` 的範圍,指到程式中不知名的地方,才會導致 d 每次都會是不同的答案。 在 C11 中,提供 [_Static_assert](https://en.cppreference.com/w/c/language/_Static_assert) 這樣的敘述來達到上述 `BUILD_BUG_ON_ZERO` 的效果,就不需要繞這麼一大圈,不過 Linux 核心考量到編譯器對語言規格的支援程度,最低期待是 C99 規格。