Namespaces
Variants

Bit-field

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

声明一个具有明确位大小的类数据成员。相邻的位域成员可能(也可能不会)被打包以共享和跨越各个字节。

位域声明是一种 类数据成员声明 ,它使用以下声明符:

标识符  (可选) 属性  (可选) : 大小 (1)
标识符  (可选) 属性  (可选) : 大小 花括号或等号初始化器 (2) (自 C++20 起)

位域的 类型 声明语法 decl-specifier-seq 引入。

attr - (since C++11) 任意数量的 属性 序列
identifier - 被声明的位域名称。该名称是可选的:未命名位域会引入指定数量的 填充位
size - 值大于或等于零的 整型常量表达式 。当大于零时,表示该位域将占用的比特位数。零值仅允许用于无名位域且具有 特殊含义
brace-or-equal-initializer - 用于该位域的 默认成员初始化器

目录

说明

位域的类型只能是整型(包括 bool )或(可能带有cv限定符的)枚举类型,无名位域不能声明为带有cv限定符的类型。

位域不能作为 静态数据成员

不存在位域的 纯右值 :左值到右值的转换始终生成位域底层类型的对象。

位域中的位数设定了其可容纳值的范围上限:

#include <iostream>
struct S
{
    // 三位无符号位域,允许值为 0...7
    unsigned int b : 3;
};
int main()
{
    S s = {6};
    ++s.b; // 在位域中存储值 7
    std::cout << s.b << '\n';
    ++s.b; // 值 8 无法容纳于此位域
    std::cout << s.b << '\n'; // 形式上由实现定义,通常输出 0
}

可能的输出:

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 文档 关于 位域