命名空间
变体
操作

位域

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++17 中弃用*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

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

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

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

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

attr - (自 C++11 起) 任意数量的属性序列
identifier - 正在声明的位域的名称。名称是可选的:未命名的位域引入指定数量的填充位
size - 一个值大于或等于零的整型常量表达式。当大于零时,这是此位域将占用的位数。值零仅允许用于未命名的位域,并具有特殊含义
brace-or-equal-initializer - 要与此位域一起使用的默认成员初始化器

目录

[编辑] 解释

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

位域不能是静态数据成员

没有位域纯右值:左值到右值的转换总是产生位域的底层类型的对象。

位域中的位数限制了它可以容纳的值的范围

#include <iostream>
 
struct S
{
    // three-bit unsigned field, allowed values are 0...7
    unsigned int b : 3;
};
 
int main()
{
    S s = {6};
 
    ++s.b; // store the value 7 in the bit-field
    std::cout << s.b << '\n';
 
    ++s.b; // the value 8 does not fit in this bit-field
    std::cout << s.b << '\n'; // formally implementation-defined, typically 0
}

可能的输出

7
0

多个相邻的位域通常被打包在一起(尽管此行为是实现定义的)

#include <bit>
#include <cstdint>
#include <iostream>
 
struct S
{
    // will usually occupy 2 bytes:
    unsigned char b1 : 3; // 1st 3 bits (in 1st byte) are b1
    unsigned char    : 2; // next 2 bits (in 1st byte) are blocked out as unused
    unsigned char b2 : 6; // 6 bits for b2 - doesn't fit into the 1st byte => starts a 2nd
    unsigned char b3 : 2; // 2 bits for b3 - next (and final) bits in the 2nd byte
};
 
int main()
{
    std::cout << sizeof(S) << '\n'; // usually prints 2
 
    S s;
    // set distinguishable field values
    s.b1 = 0b111;
    s.b2 = 0b101111;
    s.b3 = 0b11;
 
    // show layout of fields in S
    auto i = std::bit_cast<std::uint16_t>(s);
    // usually prints 1110000011110111
    // breakdown is:  └┬┘├┘└┬┘└─┬──┘└┤
    //                b1 u  a   b2  b3
    // where “u” marks the unused :2 specified in the struct, and
    // “a” marks compiler-added padding to byte-align the next field.
    // Byte-alignment is happening because b2's type is declared unsigned char;
    // if b2 were declared uint16_t there would be no “a”, b2 would abut “u”.
    for (auto b = i; b; b >>= 1) // print LSB-first
        std::cout << (b & 1);
    std::cout << '\n';
}

可能的输出

2
1110000011110111

大小为零的特殊未命名位域可以被强制中断填充。它指定下一个位域在其分配单元的开头开始

#include <iostream>
 
struct S
{
    // will usually occupy 2 bytes:
    // 3 bits: value of b1
    // 5 bits: unused
    // 2 bits: value of b2
    // 6 bits: unused
    unsigned char b1 : 3;
    unsigned char :0; // start a new byte
    unsigned char b2 : 2;
};
 
int main()
{
    std::cout << sizeof(S) << '\n'; // usually prints 2
                                    // would usually print 1 if not for
                                    // the padding break in line 11
}

可能的输出

2

如果指定的位域大小大于其类型的大小,则该值受类型限制:std::uint8_t b : 1000; 仍然会保持在范围 [0255] 内的值。额外的位是填充位

由于位域不一定从字节的开头开始,因此无法获取位域的地址。指向位域的指针和非常量引用是不可能的。当从位域初始化常量引用时,会创建一个临时对象(其类型是位域的类型),用位域的值进行复制初始化,并且引用绑定到该临时对象。

位域没有默认成员初始化器int b : 1 = 0;int b : 1 {0} 是不合法的。

(直到 C++20)

如果位域的大小和默认成员初始化器之间存在歧义,则选择形成有效大小的最长标记序列

int a;
const int b = 0;
 
struct S
{
    // simple cases
    int x1 : 8 = 42; // OK; "= 42" is brace-or-equal-initializer
    int x2 : 8 {42}; // OK; "{42}" is brace-or-equal-initializer
 
    // ambiguities
    int y1 : true ? 8 : a = 42;   // OK; brace-or-equal-initializer is absent
    int y2 : true ? 8 : b = 42;   // error: cannot assign to const int
    int y3 : (true ? 8 : b) = 42; // OK; "= 42" is brace-or-equal-initializer
    int z : 1 || new int{0};      // OK; brace-or-equal-initializer is absent
};
(自 C++20 起)

[编辑] 注释

位域的以下属性是实现定义的

  • 将超出范围的值分配或初始化带符号位域,或将带符号位域递增超过其范围所产生的值。
  • 关于类对象中位域的实际分配细节的所有内容。
  • 例如,在某些平台上,位域不跨越字节,而在其他平台上,它们会跨越。
  • 此外,在某些平台上,位域从左到右打包,而在其他平台上,它们从右到左打包。

在 C 编程语言中,位域的宽度不能超过底层类型的宽度,并且未显式声明为 signedunsignedint 位域是否为有符号或无符号是实现定义的。例如,int b : 3; 可能具有值范围 [07][-43] 在 C 中,但在 C++ 中只允许后一种选择。

[编辑] 缺陷报告

以下行为变更的缺陷报告被追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
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 文档 关于 位域