# bit-field 考慮以下 C 程式,預期輸出結果為何? ```C #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.4)** 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 bits ,而我們指定只用其中一個 bit 來放值。 但是因為 a 是 **signed integer** 所以 two complement 在 1 bit 的範圍內只能表示 0 ~ -1,因此結果是 `not one` 而決定 1-bit binary 1 為 -1 的是編譯器所定義,可以看到前面的引用提到 implementation-defined 這個詞,代表這個寫法結果是由實作品 (通常是指編譯器) 所決定,並且對於該定義有義務在文件內說明與告知。 如果題目的 `int` 並沒有說是 signed 或 unsigned ,那麼 bit-field 宣告之後究竟該是 signed / unsigned 是由編譯器所決定的。 :::success 延伸題目:舉出 Linux 核心中用到 bit field 的原始程式碼並解說其用途 ::: ### Linux Kernel: BUILD_BUG_ON_ZERO() 在 linux kernel.h 中有 include 一個 linux/build_bug.h : ```C /* * 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)): ```C 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; } ``` output 應該要是: ```C 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 每次都會是不同的答案。