[Linux Kernel慢慢學]Bit fields介紹

Posted by John on 2020-03-07
Words 1.1k and Reading Time 5 Minutes
Viewed Times

前言

之前在上一篇介紹linux kernel中build_on_zero的時候有使用到了bit fields技巧,然後我很懶的丟了一個超連結就代過了,後來越想越覺得自己對這個概念還沒是很清楚,所以痛定思痛來寫一篇介紹文順便把它搞懂。

Bit fields

bit fields是什麼?

簡單來說是可以在struct內以bit為單位來指定變數,考慮我們有一個需要紀錄4個flag的struct,如果都用bool(p.s. 題外話,c99後才在stdbool.h定義了bool這個type,我自首,我以前都沒注意…)來儲存的話則需要4*1byte = 4bytes。

但是其實每個flag只有true/false,也就是說只要一個bit來紀錄就可以了,所以我們可以用一個byte的4個bits來記錄這些flag,總共只佔了1byte。

用法如下,在struct中以冒號來指定bit field

struct bit_field_name 
{
type member_name : width;
};

注意在document有提到,bit field的type只能是以下這幾種:

  • unsigned int (Ex: unsigned int b:3 ,代表b的範圍介於0..7)
  • signed int(Ex: unsigned int b:3 ,代表b的範圍介於-4..3)
  • int,注意這裡的int是implement-defined,也就是說它是有號無號的取決於compiler
  • _Bool

下面直接舉例子一邊介紹比較相關的概念比較快。

Example1 - 使用方式

取自C - Bit Fields,當如果有兩個flag要設置的時候,使用bit field和不使用的差別。

/* define simple structure */
struct {
unsigned int widthValidated;
unsigned int heightValidated;
} status1;

/* define a structure with bit fields */
struct {
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status2;

int main( ) {
printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
return 0;
}

由於status1裡面有兩個unsigned int,所以大小是 2 * 4(unsigned int) = 8;而如果使用bit field則只有占用一個unsigned int,也就是4byte的空間。

Example2 - bit fields的對齊

bit fields的對齊可以使用unamed bit field來使得下一個bit field 對齊到下一個 unit 的 boundary,unamed bit field可以指定要pad的數量或是直接指定0對齊下一個unit,並且zero-width bit field 宣告不會使用到任何空間。

struct S {
// will usually occupy 8 bytes:
// 5 bits: value of b1
// 27 bits: unused
// 6 bits: value of b2
// 15 bits: value of b3
// 11 bits: unused
unsigned b1 : 5;
unsigned :27; // start a new unsigned int
unsigned b2 : 6;
unsigned b3 : 15;
};
int main(void)
{
printf("%zu\n", sizeof(struct S)); // usually prints 8
}

或是

struct S {
// will usually occupy 8 bytes:
// 5 bits: value of b1
// 27 bits: unused
// 6 bits: value of b2
// 15 bits: value of b3
// 11 bits: unused
unsigned b1 : 5;
unsigned :0; // start a new unsigned int
unsigned b2 : 6;
unsigned b3 : 15;
};
int main(void)
{
printf("%zu\n", sizeof(struct S)); // usually prints 8
}

Example3 - 指定struct值

參照 C/C++ 位域 Bit fields 学习心得,bit fields struct的值除了可以一個一個變數指定外,也可以透過re-mapping的方式來達成。

int* p = (int *) &b1; // 将 "位域结构体的地址" 映射至 "整形(int*) 的地址" 
*p = 0; // 清除 s1,将各成员归零

也可以透過union來指定內容,透過在union內宣告一個和struct一樣大小的variable,透過指定該變數來初始化struct的記憶體空間。

Example4 - 指定struct值的注意事項

參照stackoverflow的這篇,考慮以下程式碼:

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;
}

在指定值的時候由於alignment,c跟d都已經取到了超出0xFFFF的記憶體空間了,所以可能會取得不正確的值。

Example5 - 應用實例

參閱Practical Use of Zero-Length Bitfields這篇的回答,當兩個平台所使用的規格不同的時候,有時為了相容,會進行alignment,這時候就會用到bit fields的技巧。

結語

最後,關於bit fields有幾點要注意的:

  • 一個struct只有zero-width bit field是未定義行為
  • 無法透過pointer去操作(Because bit fields do not necessarily begin at the beginning of a byte, address of a bit field cannot be taken)
  • 不能用sizeof取得大小(但可以對整個struct做)
  • Endianness影響了bit fields的順序(The order of bit fields within an allocation unit (on some platforms, bit fields are packed left-to-right, on others right-to-left)),這取決於compiler-dependent,參閱How Endianness Effects Bitfield Packing
  • 雖然上面規定了使用的type,但其實也有人用char或unsigned char,參閱How does a bit field work with character types?

Reference


>