【问题标题】:Does C++14 define the behavior of bitwise operators on the padding bits of unsigned int?C++14 是否在 unsigned int 的填充位上定义位运算符的行为?
【发布时间】:2018-06-28 03:19:15
【问题描述】:

C++ 标准

如果C++14 实现包括在 unsigned int 的底层字节中的填充位,标准是否指定是否不能对填充位执行按位运算?

此外,C++14 标准是否指定是否相等和关系 运营商必须忽略填充位?

指南

如果在这方面缺乏规范,是否存在某种形式的 对这些运算符在填充位上的预期行为达成共识?

我在 Stack Overflow 上发现了相互矛盾的答案。 Lightness Races in Orbitecatmur 表示位运算符不适用于算术,因为它们应用于所有位(包括填充位),而 ChristophBartek Banachewicz 表示位运算符适用于整数的逻辑值并忽略填充。

参考文献

相关答案:关于填充位的存在 (1, 2, 3), 由于缺乏明确的 C++ 规范 (4)。

C++14 中填充位的定义 - § 3.9.1 - 基本类型:

对于窄字符类型,对象表示的所有位都参与值表示。对于无符号窄字符类型,值表示的所有可能的位模式都表示数字。这些要求不适用于其他类型。

C++14 中对象表示和值表示的定义 - § 3.9 - 类型:

T 类型对象的 对象表示T 类型对象占用的 Nunsigned char 对象的序列,其中 N 等于sizeof(T)。对象的值表示是一组保存T 类型值的位。对于普通可复制类型,值表示是对象表示中的一组位,它们确定 ,它是实现定义的一组值的一个离散元素。44

脚注 44) 意图是 C++ 的内存模型与 ISO/IEC 9899 编程语言 C 的内存模型兼容。

C++14 中位与的定义 - § 5.11 - 位与运算符:

执行通常的算术转换;结果是操作数的按位与函数。该运算符仅适用于整数或无范围枚举操作数。

C++14 中加法的定义 - § 5.7 - 加法运算符:

对算术或枚举类型的操作数执行通常的算术转换。此外,[...] 两个操作数都应具有算术或无范围枚举类型 [...]。二进制+ 运算符的结果是操作数的和。

【问题讨论】:

  • 你怎么知道?你唯一能得到的是值,它不包括填充位。
  • @EJP 通过检查对象表示?
  • 按位运算是根据值位定义的。它们根本无法访问填充位(除非您将内存重新解释为无符号字符数组)。
  • Related What's the result of a & b? 谈到了按位运算中的一些规范不足。
  • 您知道任何使用填充 int 的实际架构吗?我不是。这个概念有点陌生。

标签: c++ c++14 bitwise-operators unsigned-integer unspecified-behavior


【解决方案1】:

首先,C++ 标准本身几乎没有提到填充位。基本上所有关于填充位的讨论都来自基本文档(即 C 标准)。

所以真正的问题是 C 标准对事物的描述。它的脚注 54 给出了一个相当简洁的填充位总结:

填充位的某些组合可能会生成陷阱表示,例如,如果一个填充位是奇偶校验位。无论如何,除了作为异常条件(例如溢出)的一部分之外,没有任何对有效值的算术运算可以生成陷阱表示。所有其他填充位组合都是由值位指定的值的替代对象表示。

操作员可能会改变一个大的填充。明显的情况是表示奇偶校验的填充位。如果您更改一个值的奇偶校验,奇偶校验位将更改为匹配。

“值的替代对象表示”部分基本上意味着只要您保持“在界限内”,填充位就不会影响您的结果。例如,如果比较两个值,则仅使用表示位来确定结果(6.2.6.1/4):

具有相同对象表示的两个值(NaN 除外)比较相等,但比较相等的值可能具有不同的对象表示。

您必须注意的时间和地点大多涉及未定义或实现定义的行为。例如,如果您将一个值存储到联合中的一个值中,然后在联合中检索另一个值,则第二个可能会将填充位设置为陷阱表示,因此即使以这种方式查看值也可能会使您的程序(或其他)。

同样,如果您将两个值 memcpy 分别放入一个 unsigned char 缓冲区,则这些字节的某些位可能比较不相等,即使它们表示的值确实比较相等。

即使您从不直接使用mempy 也会让您感到困扰的一个地方是一些比较和交换运算符。它们使用memcpymemcmp 进行底层操作,因此即使表示的值相等,它们也会进行比较不相等:

[atomics.types.operations]/23:

如果基础类型具有填充位、陷阱位或相同值的替代表示,则比较和交换操作的 memcpy 和 memcmp 语义可能会导致比较等于 operator== 的值比较失败。因此,使用 compare_exchange_strong 时应格外小心。另一方面, compare_exchange_weak 应该快速收敛。

旁注:两个大引号是描述性的,不是规范的——从规范的角度来看,填充位几乎没有意义;几乎任何可能暴露填充位或其值的东西都涉及实现定义或未定义的行为。这里唯一的规范引用基本上是说:“填充位无效。”

【讨论】:

  • 在您的第一句话中,有:“对有效值的算术运算不能生成陷阱表示”。我想知道按位运算是否被视为“算术运算”或“对原始字节的运算”。
  • @RalphS:我认为标准中没有这样的定义,但它们是根据算术定义的(例如,“结果的值是 E1 × 2^E2 ")。
  • 遗憾的是,这仅适用于轮班。现在我对这个问题有了更好的理解,我想将问题更改为:“C++14 是否保证对有效无符号整数的按位运算不能生成陷阱表示?”。答案基本上是“不。但可以假设它是可以的,因为只有傻瓜才会编写不尊重该原则的编译器。”可以改变问题吗? (我是新来的。)对于那些想要对 unsigned int 值进行数学运算的人来说,当前措辞中的问题有点用处。
  • 另一方面,当前措辞中的问题对于想要对大量原始内存 (XOR cipher) 执行按位异或的人可能很有用。在这种情况下,必须使用 unsigned char 类型(unsigned char 表示中不能有填充位)。
【解决方案2】:

如果一个实现为包含填充位的整数类型指定了一种存储格式,它可以在写入对象时将任何它喜欢的内容写入这些位,并且可以对这些位必须保持的值施加它认为合适的任何要求,表现如果不满足该要求,则以任意方式进行,受两个约束:

  1. 如果对此类对象的任何写入都产生了特定的位模式,则该位模式必须是可接受的,并且在从该类型的任何对象读取时必须产生相同的值。

  2. 如果整数类型对象的所有位都为零,则该对象必须被视为有效并且必须读取为零。

如果实现忽略读取时的填充位,按位运算符可能会影响它们,也可能不会以实现认为合适的任何方式影响它们。如果实现在多字节整数中的“1”位总数为奇数时进行陷阱,但始终写入使总奇偶校验为偶数的填充位值,则需要按位运算符来计算奇偶校验位根据数据位并适当地写入。

【讨论】:

  • 这是来自标准的东西,还是其他地方?引用您的来源。
  • 我想我明白了。如果我错了,请纠正我,但我认为 C++ 标准并不详尽。编译器可以 100% 符合标准,但仍然没有任何意义。尝试编写可以与此类编译器正常工作的代码是没有意义的。所以在某种程度上,C++ 的正确实现需要两件事:尊重标准中描述的要求尊重常识规则。其中一项规则是确保按位运算符按每个人的预期工作,不受填充位的干扰。
  • @RalphS:不幸的是,虽然 C 和 C++ 标准最初是基于编译器编写者可以依赖于行使常识的假设而编写的,并且没有必要强制执行明显会使在某些平台上,编译器编写者越来越多地将缺乏强制要求视为程序员有权期待此类行为的迹象,编译器编写者不应该感到有义务支持它们。不幸的是,C 和 C++ 的语义正以“优化”的名义受到侵蚀。
  • @1201ProgramAlarm:对于原始对象和普通旧数据结构 (PODS),标准要求复制值的所有字节将复制该值。因此,如果将值 42 存储到 unsigned short 中会将一些位序列写入其存储(可能包括填充位),则将该位序列复制到另一个 unsigned short 必须使其保持值 42。包括整数类型的类型是允许有陷阱表示,即不代表有效值的位序列。
猜你喜欢
  • 1970-01-01
  • 2011-05-19
  • 2023-04-11
  • 2023-03-20
  • 1970-01-01
  • 2021-02-06
  • 1970-01-01
  • 2014-09-16
相关资源
最近更新 更多