C++标准
如果 C++14 实现包括在unsigned int
的底层字节中填充位,标准是否指定是否不得对填充位执行按位操作?
此外,C++14 标准是否指定了相等和关系 运算符必须忽略填充位吗?
指引
如果在这个问题上缺乏规范,是否有某种 就这些运算符在填充位上的预期行为达成共识?
我在堆栈溢出上发现了相互矛盾的答案。Lightness Races in Orbit和ecatmur表示,按位运算符不适合算术,因为它们适用于所有位(包括填充位),而Christoph和Bartek Banachewicz表示,按位运算符处理整数的逻辑值并忽略填充。
引用
相关回答:关于填充位的存在 +1O 2, 3), 关于缺乏明确C++规范 (4).
C++14 - § 3.9.1 中填充位的定义 - 基本类型:
对于窄字符类型,对象表示形式的所有位都参与值表示形式。对于无符号窄字符类型,值表示形式的所有可能的位模式都表示数字。这些要求不适用于其他类型的类型。
C++14 - § 3.9 - 类型中对象表示和值表示的定义:
类型为
T
的对象的对象表示是类型T
的对象占用的 N 个unsigned char
对象的序列,其中N等于sizeof(T)
个。对象的值表示形式是保存类型T
的值的位集。对于简单可复制的类型,值表示形式是对象表示形式中的一组位,用于确定值,该值是实现定义的值集的一个离散元素。44脚注44)目的是C++的内存模型与 ISO/IEC 9899 编程语言 C 的内存模型兼容。
C++14 - § 5.11 中按位 AND 的定义 - 按位 AND 运算符:
执行通常的算术转换;结果是操作数的按位 AND 函数。运算符仅适用于整型或无作用域枚举操作数。
C++14 - § 5.7 - 加法运算符中的加法定义:
通常的算术转换是对算术或枚举类型的操作数执行的。此外,[...] 两个操作数都应具有算术或无作用域枚举类型 [...]。二进制
+
运算符的结果是操作数的总和。
首先,C++标准本身几乎没有提到填充位。基本上所有关于填充位的讨论都来自基本文档(即 C 标准)。
所以真正的问题是C标准对事物的看法。它的脚注 54 对填充位进行了相当简洁的总结:
填充位的某些组合可能会生成陷印表示形式,例如,如果一个填充位是奇偶校验位。无论如何,对有效值的任何算术运算都不能生成陷阱表示形式,除非作为异常条件(如溢出)的一部分。填充位的所有其他组合都是值位指定的值的替代对象表示形式。
运算符可能会更改较大的填充。显而易见的情况是表示奇偶校验的填充位。如果更改值的奇偶校验,奇偶校验位将更改为匹配。
">值的替代对象表示"部分基本上意味着只要您保持"边界",填充位就不会影响您的结果。例如,如果比较两个值,则仅使用表示位来确定结果 (6.2.6.1/4):
具有相同对象表示的两个值(NaN 除外)比较相等,但比较相等的值可能具有不同的对象表示。
您必须注意的时间和地点主要涉及未定义或实现定义的行为。例如,如果将一个值存储到联合中的一个值中,然后在联合中检索另一个值,则第二个值可能会将填充位设置为陷阱表示形式,因此即使以这种方式查看值也可能会使您的程序崩溃(或其他什么)。
同样,如果要采用两个值,memcpy
每个值到无符号字符的缓冲区,则这些字节的某些位可能会比较为不相等,即使它们表示的值确实相等。
即使您从不直接使用mempy
,也会咬到您的一个地方是一些比较和交换运算符。它们对基础操作使用memcpy
和memcmp
,因此即使表示的值相等,它们也会受到不相等的比较:
[atomics.types.operations]/23:
如果基础类型具有相同值的填充位、陷阱位或备用表示形式,则比较和交换操作的 memcpy 和 memcmp 语义可能会导致与运算符 == 比较相等的值的比较失败。因此,compare_exchange_strong应格外小心地使用。另一方面,compare_exchange_weak应该迅速收敛。
旁注:这两个大引号是描述性的,而不是规范性的——从规范的角度来看,填充位几乎没有意义;几乎任何可能暴露填充位或其值的东西都涉及实现定义或未定义的行为。这里唯一的规范性引用基本上是说:"填充位没有效果。
如果实现为包含填充位的整数类型指定了存储格式,则它可以在写入对象时将它喜欢的任何内容写入此类位,并且可以将它认为合适的任何要求强加于此类位必须包含的值,如果不满足该要求,则以任意方式运行,但受两个约束:
-
如果对此类对象的任何写入都产生了特定的位模式,则该位模式必须是可接受的,并且在从该类型的任何对象读取时必须产生相同的值。
-
如果整数类型对象的所有位均为零,则必须将该对象视为有效且必须读取零。
如果实现在读取时忽略填充位,则按位运算符可能会以实现认为合适的任何方式影响它们或不影响它们。 如果实现在多字节整数中的"1"位总数为奇数时进行捕获,但始终写入使总奇偶校验为偶数的填充位值,则需要按位运算符基于数据位计算奇偶校验位并适当地写入它。