C ++ 11中的第9.6 / 3节非常清楚:“非const引用不应绑定到位字段。”这项禁令背后的动机是什么?
我知道直接将引用绑定到位域是不可能的。但如果我宣布这样的话,
struct IPv4Header {
std::uint32_t version:4, // assumes the IPv4 Wikipedia entry is correct
IHL:4,
DSCP:6,
ECN:2,
totalLength:16;
};
为什么我不能说这个?
IPv4Header h;
auto& ecn = h.ECN;
我希望底层代码实际绑定到包含我感兴趣的位的整个std::uint32_t
,并且我希望读取和写入操作生成代码以进行适当的屏蔽。结果可能是大而慢,但在我看来它应该工作。这与标准表示对const
位域的引用有效(再次来自9.6 / 3)的方式一致:
如果初始化器为参考 类型const T&是一个引用位字段的左值,引用绑定到临时初始化为 保持位域的值;引用不直接绑定到位字段。
这表明写入位域是问题,但我不知道它是什么。我认为必要的屏蔽可能会在多线程代码中引入竞争,但是,根据1.7 / 3,非零宽度的相邻位域被认为是用于多线程的单个对象。在上面的示例中,IPv4Header
对象中的所有位域都将被视为单个对象,因此根据定义,在读取其他字段时尝试修改字段的多线程代码已经很有效。
我显然遗漏了一些东西。它是什么?
答案 0 :(得分:10)
您无法对位域进行非const
引用,原因与您无法使用&
的地址相同:其实际地址不一定与{{1}对齐},它在定义上是C ++抽象机器中最小的可寻址内存单元。您可以对char
进行引用,因为编译器可以复制该值,因为它不会被突变。
考虑单独编译的问题。采用const
的函数需要使用相同的代码才能对任何const uint32_t&
进行操作。如果普通值和位域值需要不同的写行为,则该类型不会编码足够的信息,以使函数在两者上都能正常工作。
答案 1 :(得分:9)
非const引用不能绑定到位字段,原因与指针不能指向位字段的原因相同。
虽然未指定引用是否占用存储,但很明显,在非平凡的情况下,它们是伪装的指针实现,并且这种引用的实现是由该语言的作者“预期”实现的。就像指针一样,引用必须指向可寻址的存储单元。将非const引用绑定到不可寻址的存储单元是不可能的。由于非const引用需要直接绑定,因此非const引用不能绑定到位字段。
产生指向位字段的指针/引用的唯一方法是实现某种“超级指针”,除了存储中的实际地址之外还包含某种位偏移和位 - 宽度信息,以告诉写代码修改哪些位。请注意,此附加信息必须存在于所有数据指针类型中,因为C ++中没有“指向/引用位域”的类型。这基本上等同于实现更高级别的存储寻址模型,与底层OS /硬件平台提供的寻址模型完全脱节。 C ++语言从来没有打算从纯粹的效率考虑中要求从底层平台那种抽象。
一种可行的方法是引入一个单独的指针/引用类别,例如“指向/引用位域”,它将具有比普通数据指针/引用更复杂的内部结构。这些类型可以从普通数据指针/引用类型转换,但不能相反。但它似乎不值得。
在实际情况中,当我必须处理打包成比特和比特序列的数据时,我通常更喜欢手动实现比特字段并避免语言级别的比特字段。位字段的名称是编译时实体,不可能选择任何类型的运行时。当需要运行时选择时,更好的方法是声明一个普通的uint32_t
数据字段,并手动管理其中的各个位和位组。这种手动“位字段”的运行时选择很容易通过掩码和移位来实现(两者都可以是运行时值)。基本上,这接近上述“超级指针”的手动实现。