为什么禁止对位域的非常量引用?

问题描述 投票:0回答:2

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

我显然遗漏了一些东西。这是什么?

c++ c++11 bit-fields
2个回答
15
投票

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

虽然没有指定引用是否占用存储空间,但很明显,在非平凡的情况下,它们被伪装成指针来实现,而这种引用的实现是语言作者“有意为之”的。就像指针一样,引用必须指向一个可寻址的存储单元。在普通硬件中,最小的可寻址存储单元是按字节(而不是按位)。不可能将非常量引用绑定到不可寻址的存储单元。由于非常量引用需要直接绑定,因此非常量引用不能绑定到位域。您可以使用 const 引用只是因为允许编译器复制该值。

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

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

在实际情况下,当我必须处理打包成位和位序列的数据时,我通常更喜欢手动实现位域并避免语言级别的位域。位域的名称是一个编译时实体,不可能进行任何类型的运行时选择。当需要运行时选择时,更好的方法是声明一个普通的

uint32_t
数据字段并手动管理其中的各个位和位组。这种手动“位域”的运行时选择很容易通过掩码和移位(两者都可以是运行时值)来实现。基本上,这接近于手动实现上述“超级指针”。


10
投票

您不能对位域进行非

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

考虑单独编译的问题。采用

const uint32_t&
的函数需要使用相同的代码来操作任何
const uint32_t&
。如果普通值和位域值需要不同的写入行为,那么该类型没有编码足够的信息以使函数在两者上都能正常工作。

© www.soinside.com 2019 - 2024. All rights reserved.