Post

bit-fileds and data alignment

简介

位域的声明可以指定类或结构体中的数据成员存储位数, 通常用于节省不必要的空间开销. 相邻的成员可能被打包共享一个字节, 也可能被分开横跨多个字节. 真是晦涩难懂的定义呢

用法如下: 在所有对非静态成员变量的声明后加 : <constexpr unsigned int>

1
2
3
4
5
6
7
struct S {
    unsigned int a : 3; // 占 3 bits, 因此只能表示 [0, 2^3 - 1]
    unsigned int   : 2; // 占 2 bits, 未使用作为填充
    unsigned int b : 6; // 占 6 bits, 因此只能表示 [0, 2^6 - 1]
    unsigned int c : 5; // 占 5 bits, 因此只能表示 [0, 2^5 - 1]
    unsigned int d : 8; // 占 8 bits, 因此只能表示 [0, 2^8 - 1]
};

其中有一种特殊位域(无名位域)声明, 特点为不具备名称, 作用是指定一定位为不使用的位(作为填充位), 强制下一个位域在内存分配边界对齐.

内存对齐

然而, 虽然声明了位域, 结构体实际占位并不能通过所有成员之和得到, 因为编译器会给出一定的填充占位 (padding) 从而满足内存对齐.

这里编译器给出的填充占位与无名位域不同, 并非代码中显式声明的部分, 是编译器的一种优化.

这将从 CPU 结构讲起, 总所周知 (不知道的可以看计算机组成原理, 我猜的我真没看过), CPU 存在一个结构叫做总线 (bus). 通常而言, 总线获取数据不会逐个位获取 (by bit), 而是多字节获取 (by multiple bytes). 具体是多少个字节取决于总线位宽 (实际就是平台).

1
2
3
4
5
6
struct S {
    unsigned int a : 3;
    unsigned int b : 6;
    unsigned int c : 5;
    unsigned int d : 8;
};

内存分配情况如下:

  • 首个双字节: a + b + c + 00, 其中末尾两位为编译器自行填充
  • 第二个双字节: d + 00000000, 其中末尾八位为编译器自行填充

接着加入无名位域

1
2
3
4
5
6
7
struct S {
    unsigned int a : 3;
    unsigned int   : 2;
    unsigned int b : 6;
    unsigned int c : 5;
    unsigned int d : 8;
};

内存分配情况如下:

  • 首个双字节: a + 00 + 000…, a 后两位为显式声明的占位, 其余的为编译器自行填充
  • 第二个双字节: b + c + 000…, 其余的为编译器自行填充
  • 第三个双字节: d + 000…, 其余的为编译器自行填充

参考文献

This post is licensed under CC BY 4.0 by the author.