Bit-field
声明一个具有明确位大小的类数据成员。相邻的位域成员可能(也可能不会)被打包以共享和跨越各个字节。
位域声明是一种 类数据成员声明 ,它使用以下声明符:
标识符
(可选)
属性
(可选)
:
大小
|
(1) | ||||||||
标识符
(可选)
属性
(可选)
:
大小
花括号或等号初始化器
|
(2) | (自 C++20 起) | |||||||
位域的 类型 由 声明语法 的 decl-specifier-seq 引入。
| attr | - | (since C++11) 任意数量的 属性 序列 |
| identifier | - | 被声明的位域名称。该名称是可选的:未命名位域会引入指定数量的 填充位 |
| size | - | 值大于或等于零的 整型常量表达式 。当大于零时,表示该位域将占用的比特位数。零值仅允许用于无名位域且具有 特殊含义 |
| brace-or-equal-initializer | - | 用于该位域的 默认成员初始化器 |
目录 |
说明
位域的类型只能是整型(包括 bool )或(可能带有cv限定符的)枚举类型,无名位域不能声明为带有cv限定符的类型。
位域不能作为 静态数据成员 。
不存在位域的 纯右值 :左值到右值的转换始终生成位域底层类型的对象。
位域中的位数设定了其可容纳值的范围上限:
可能的输出:
7 0
多个相邻的位域通常会被打包在一起(尽管这一行为是由实现定义的):
#include <bit> #include <cstdint> #include <iostream> struct S { // 通常占用2个字节: unsigned char b1 : 3; // 第1个字节的前3位是b1 unsigned char : 2; // 接下来的2位(在第1个字节中)被标记为未使用 unsigned char b2 : 6; // b2的6位无法放入第1个字节 => 从第2个字节开始 unsigned char b3 : 2; // b3的2位 - 第2个字节中最后剩余的位 }; int main() { std::cout << sizeof(S) << '\n'; // 通常输出2 S s; // 设置可区分的字段值 s.b1 = 0b111; s.b2 = 0b101111; s.b3 = 0b11; // 显示S中字段的布局 auto i = std::bit_cast<std::uint16_t>(s); // 通常输出 1110000011110111 // 分解为: └┬┘├┘└┬┘└─┬──┘└┤ // b1 u a b2 b3 // 其中“u”标记结构体中指定的未使用:2位, // “a”标记编译器添加的填充位,用于将下一个字段对齐到字节边界。 // 发生字节对齐是因为b2的类型声明为unsigned char; // 如果b2声明为uint16_t,则不会存在“a”,b2将紧接“u”。 for (auto b = i; b; b >>= 1) // 从最低有效位开始打印 std::cout << (b & 1); std::cout << '\n'; }
可能的输出:
2 1110000011110111
特殊的无名位域(大小为零)可用于强制中断填充。它指定下一个位域从其分配单元的开头开始:
#include <iostream> struct S { // 通常占用 2 字节: // 3 位:b1 的值 // 5 位:未使用 // 2 位:b2 的值 // 6 位:未使用 unsigned char b1 : 3; unsigned char :0; // 开始新字节 unsigned char b2 : 2; }; int main() { std::cout << sizeof(S) << '\n'; // 通常输出 2 // 若非第 11 行的填充中断 // 通常会输出 1 }
可能的输出:
2
如果指定位域的大小超过其类型的大小,该值将受类型限制:
std::
uint8_t
b
:
1000
;
仍将保持
[
0
,
255
]
范围内的值。额外位将作为
填充位
。
由于位域不一定从字节的起始处开始,因此无法获取位域的地址。指向位域的指针和非常量引用是不可能的。当从位域 初始化常量引用 时,会创建一个临时对象(其类型为位域的类型),用位域的值进行拷贝初始化,并将引用绑定到该临时对象。
|
位字段没有 默认成员初始化器 : int b : 1 = 0 ; 和 int b : 1 { 0 } 的写法是错误的。 |
(C++20 前) |
|
当位字段宽度与默认成员初始化器存在歧义时,会选择构成有效宽度的最长token序列: int a; const int b = 0; struct S { // 简单情况 int x1 : 8 = 42; // 正确;"= 42" 是花括号或等号初始化器 int x2 : 8 {42}; // 正确;"{42}" 是花括号或等号初始化器 // 歧义情况 int y1 : true ? 8 : a = 42; // 正确;不存在花括号或等号初始化器 int y2 : true ? 8 : b = 42; // 错误:不能对 const int 赋值 int y3 : (true ? 8 : b) = 42; // 正确;"= 42" 是花括号或等号初始化器 int z : 1 || new int{0}; // 正确;不存在花括号或等号初始化器 }; |
(C++20 起) |
注释
位域的以下属性是 实现定义的 :
- 将超出范围的值赋值或初始化给有符号位域时产生的值,或者将有符号位域递增超过其范围时的情况。
- 类对象中位域实际分配细节的所有相关内容。
-
- 例如,在某些平台上,位域不会跨字节存储,而在其他平台上则会跨字节。
- 此外,在某些平台上,位域按从左到右的顺序打包,而在其他平台上则按从右到左的顺序打包。
在C编程语言中,位域的宽度不能超过其基础类型的宽度,且未显式声明为
signed
或
unsigned
的
int
位域究竟带符号与否由具体实现定义。例如,
int
b
:
3
;
在C语言中可能具有
[
0
,
7
]
或
[
-
4
,
3
]
的值范围,但在C++中仅允许后一种选择。
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 324 | C++98 | 未指明对位域的赋值返回值是否为位域 | 为可能返回左值的运算符添加位域规范 |
| CWG 739 | C++98 | 既未声明为 signed 也未声明为 unsigned 的位域的有符号性由实现定义 | 与底层类型保持一致 |
| CWG 2229 | C++98 | 无名位域可以用cv限定类型声明 | 已禁止 |
| CWG 2511 | C++98 | 位域类型不允许使用cv限定符 | 位域可以具有cv限定的枚举类型 |
参考文献
- C++23 标准 (ISO/IEC 14882:2024):
-
- 11.4.10 位域 [class.bit]
- C++20 标准 (ISO/IEC 14882:2020):
-
- 11.4.9 位域 [class.bit]
- C++17 标准 (ISO/IEC 14882:2017):
-
- 12.2.4 位域 [class.bit]
- C++14 标准 (ISO/IEC 14882:2014):
-
- 9.6 位域 [class.bit]
- C++11 标准 (ISO/IEC 14882:2011):
-
- 9.6 位域 [class.bit]
- C++03 标准 (ISO/IEC 14882:2003):
-
- 9.6 位域 [class.bit]
- C++98 标准 (ISO/IEC 14882:1998):
-
- 9.6 位域 [class.bit]
另请参阅
|
实现定长位数组
(类模板) |
|
|
空间优化的动态位集
(类模板特化) |
|
| 位操作 (C++20) | 用于访问、操作和处理单个位及位序列的工具 |
|
C 文档
关于
位域
|
|