【问题标题】:Padding bits in unsigned integers and bitwise operations in C89无符号整数中的填充位和 C89 中的按位运算
【发布时间】:2011-05-27 09:47:42
【问题描述】:

我有很多对无符号整数执行按位运算的代码。我编写代码时假设这些操作是在没有任何填充位的固定宽度整数上进行的。例如一个 32 位无符号整数数组,其中所有 32 位可用于每个整数。

我希望让我的代码更具可移植性,并且我专注于确保我是 C89 compliant(在这种情况下)。我遇到的问题之一是可能的填充整数。举个极端的例子,取自GMP manual

但是,在 Cray 矢量系统上,可能会注意到 short 和 int 始终存储在 8 个字节中(sizeof 表示这一点)但仅使用 32 或 46 位。 nails 功能可以解决这个问题,例如传递8*sizeof(int)-INT_BIT

我还在其他地方读到过这种类型的填充。实际上,我昨晚读到了 SO 上的一篇文章(请原谅,我没有链接,我将从记忆中引用类似的内容)如果你有一个带有 60 个可用位的双精度位,那么其他 4 个可以用于填充,这些填充位可以用于某些内部目的,因此它们不能被修改。


假设我的代码是在一个平台上编译的,其中 unsigned int 类型的大小为 4 个字节,每个字节为 8 位,但最重要的 2 位是填充位。 UINT_MAX 在这种情况下会是 0x3FFFFFFF (1073741823) 吗?

#include <stdio.h>
#include <stdlib.h>

/* padding bits represented by underscores */
int main( int argc, char **argv )
{
    unsigned int a = 0x2AAAAAAA; /* __101010101010101010101010101010 */
    unsigned int b = 0x15555555; /* __010101010101010101010101010101 */
    unsigned int c = a ^ b; /* ?? __111111111111111111111111111111 */
    unsigned int d = c << 5; /* ??  __111111111111111111111111100000 */
    unsigned int e = d >> 5; /* ?? __000001111111111111111111111111 */
    
    printf( "a: %X\nb: %X\nc: %X\nd: %X\ne: %X\n", a, b, c, d, e );
    return 0;
}
  • 用填充位异或两个整数是否安全?
  • 我不会对填充位进行异或吗?

我在 C89 中找不到这种行为。

此外,c 变量是否保证为 0x3FFFFFFF,或者例如,如果 a 或 b 中的两个填充位都打开,c 是否会是 0xFFFFFFFF

de 的问题相同。我是否通过移位来操作填充位? 我希望在下面看到这一点,假设 32 位,其中 2 个最高有效位用于填充,但我想知道这样的事情是否得到保证:

a: 2AAAAAAA
b: 15555555
c: 3FFFFFFF
d: 3FFFFFE0
e: 01FFFFFF

填充位总是最高有效位还是最低有效位?


编辑 2010 年 12 月 19 日下午 5 点 EST:Christoph 回答了我的问题。谢谢!
我还问过(上面)填充位是否总是最重要的位。这在 C99 标准的基本原理中被引用,答案是否定的。我玩得很安全,并假设 C89 也是如此。以下是 C99 基本原理对第 6.2.6.2 节(整数类型的表示)所说的具体内容:

用户可以在无符号整数类型中访问填充位。例如,假设一台机器使用一对 16 位的 short(每个都有自己的符号位)组成一个 32 位的 int,并且在这个 32 位的 int 中使用时,忽略低位 short 的符号位。然后,作为 32 位有符号整数,有一个填充位(在 32 位中间)在确定 32 位有符号整数的值时会被忽略。但是,如果这个 32 位项被视为 32 位无符号整数,则该填充位对用户程序是可见的。 C 委员会被告知有一台机器可以以这种方式工作,这就是在 C99 中添加填充位的原因之一。

脚注 44 和 45 提到奇偶校验位可能是填充位。委员会不知道有任何机器在整数内具有用户可访问的奇偶校验位。因此,委员会不知道有任何机器将奇偶校验位视为填充位。


编辑 2010 年 12 月 28 日下午 3 点 EST:我在几个月前发现了一个关于 comp.lang.c 的有趣讨论。

Dietmar 提出的一点我觉得很有趣:

请注意,填充位对于陷阱表示的存在不是必需的;不代表对象类型值的值位组合也可以。

【问题讨论】:

  • 最后的评论是错误的。通过简单的计数参数,填充位对于陷阱表示的存在是必要的。 C 只允许无符号值的纯二进制表示,并且只允许有符号值的 3 种可能的表示,当值适合时,所有这些都与表示中对应的无符号类型一致。
  • R,我对 Dietmar 的引用来自后面的讨论,当时提到如果不支持负零 -0,它可能是一个陷阱表示,并且按位补码运算符可能会导致这样的表示。我假设一个例子是~(int)0,其中有符号整数表示是一个的补码,并且可能会根据实现导致陷阱表示。
  • 虽然我刚才想到,符号位在标准中与值位是分开提到的,因此 -0 也需要符号位
  • @AQG:我同意你的 cmets。在非二进制补码实现中,只有一种可能的陷阱表示不依赖于填充位。

标签: c bit-manipulation padding bitwise-operators


【解决方案1】:

按位运算(如算术运算)对值进行运算并忽略填充。该实现可能会或可能不会修改填充位(或在内部使用它们,例如作为奇偶校验位),但可移植的 C 代码将永远无法检测到这一点。任何值(包括UINT_MAX)都不会包含填充。

如果您使用sizeof (int) * CHAR_BIT 之类的东西,然后尝试使用移位来访问所有这些位,那么整数填充可能会导致问题。如果您想要可移植,要么只使用 (unsigned) char,固定大小的整数(C99 加法),要么以编程方式确定值位的数量。这可以在编译时使用预处理器通过比较 UINT_MAX 与 2 的幂或在运行时使用位操作来完成。

编辑:

C90 根本没有提到整数填充,但据我所知,“不可见”前面或后面的整数填充位不应该违反标准(我没有通过所有相关部分来确保这是确实如此); C99 基本原理中提到的混合填充和值位可能存在问题,因为否则,不需要更改标准。

关于用户可访问的含义:填充位是可访问的,只要您可以通过在((unsigned char *)&amp;foo)[…] 上使用位操作获得foo 的任何位(包括填充)。但是,在修改填充位时要小心:结果不会改变整数的值,但可能会创建一个陷阱表示。在 C90 的情况下,这是隐式未指定的(根本没有提到),在 C99 的情况下,它是实现定义的。

但这并不是引用的基本原理:引用的架构通过两个 16 位整数表示 32 位整数。对于无符号类型,生成的整数有 32 个值位,精度为 32;对于有符号整数,它只有 31 个值位,精度为 30:16 位整数的一个符号位用作 32 位整数的符号位,另一个被忽略,从而创建由值位包围的填充位。现在,如果您将 32 位有符号整数作为无符号整数访问(这是明确允许的并且不违反 C99 别名规则),则填充位将成为(用户可访问的)值位。

【讨论】:

  • 完全正确。这意味着示例代码很好(唯一的可移植性问题是假设UINT_MAX0x3FFFFFFF)。
  • 感谢 Christoph 我已将您的答案标记为正确。我假设您引用 unsigned char 因为它不能有任何填充位。我知道这对于 C99 是正确的,你能确认它对于 C89 吗?另外我想知道您是否可以权衡我最近的编辑。 C99 的基本原理是“用户可以在无符号整数类型中访问填充位”。 “用户可访问”是否意味着我的程序可以以某种方式修改无符号整数中的填充位,这与 C89 有什么关系?它只是被认为是未指定的吗?再次感谢
  • 您好 Christoph,感谢您更新您的答案。我阅读了您的编辑,非常有帮助。
  • @AQG:填充位永远可访问的唯一方法是通过unsigned char [sizeof(type)]类型的表示。如果您在unsigned char 的值位中看不到任何位,那么出于所有意图和目的,该位不存在。因此unsigned char“没有”填充位,即使它们存在于硬件上,程序也看不到它们,因此“不存在”。
  • @Christoph 我正在阅读 C99 规范,它在 6.5.7.4 中声明 The result of E1 &lt;&lt; E2 is E1 left-shifted E2 bit positions,没有说移位仅发生在 E1 的对象表示的值位上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-23
  • 1970-01-01
  • 2020-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多