14

C++11 中的第 9.6/3 节非常清楚:“非常量引用不应绑定到位域。” 这个禁令背后的动机是什么?

我了解无法直接将引用绑定到位域。但如果我声明这样的事情,

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对象中的所有位域都将被视为单个对象,因此根据定义,尝试在读取其他字段的同时修改字段的多线程代码已经很活泼了。

我显然错过了一些东西。它是什么?

4

2 回答 2

12

非常量引用不能绑定到位域,原因与指针不能指向位域的原因相同。

虽然没有指定引用是否占用存储空间,但很明显,在非平凡的情况下,它们被伪装成指针,并且这种引用的实现是语言作者“有意”的。就像指针一样,引用必须指向一个可寻址的存储单元。将非常量引用绑定到不可寻址的存储单元是不可能的。由于非常量引用需要直接绑定,因此非常量引用不能绑定到位字段。

生成可以指向位域的指针/引用的唯一方法是实现某种“超级指针”,除了存储中的实际地址之外,它还包含某种位偏移和位宽信息,为了告诉编写代码要修改哪些位。请注意,此附加信息必须存在于所有数据指针类型中,因为 C++ 中没有“指向位域的指针/引用”这样的类型。这基本上相当于实现了一个更高级别的存储寻址模型,完全脱离了底层操作系统/硬件平台提供的寻址模型。出于纯粹的效率考虑,C++ 语言从未打算要求从底层平台进行这种抽象。

一种可行的方法是引入一个单独的指针/引用类别,例如“指向位域的指针/引用”,它具有比普通数据指针/引用更复杂的内部结构。这种类型可以从普通的数据指针/引用类型转换,但不能反过来。但这似乎不值得。

In practical cases, when I have to deal with data packed into bits and sequences of bits, I often prefer to implement bit-fields manually and avoid language-level bit-fields. The name of bit-field is a compile-time entity with no possibility of run-time selection of any kind. When run-time selection is necessary, a better approach is to declare an ordinary uint32_t data field and manage the individual bits and groups of bits inside it manually. The run-time selection of such manual "bit-field" is easily implemented through masks and shifts (both can be run-time values). Basically, this is close to manual implementation of the aforementioned "superpointers".

于 2013-07-12T06:46:27.140 回答
10

您不能const对位域进行非引用,原因与您不能将其地址与 相同的原因&:它的实际地址不一定与 对齐char,这在定义上是 C++ 抽象机中最小的可寻址内存单元。您const可以引用它,因为编译器可以自由复制该值,因为它不会被变异。

考虑单独编译的问题。采用 a 的函数const uint32_t&需要使用相同的代码来操作任何const uint32_t&. 如果普通值和位域值需要不同的写入行为,则该类型没有编码足够的信息以使函数在两者上都正常工作。

于 2013-07-12T05:36:24.487 回答