无符号整数中的填充位和C89中的位操作

17
我有很多代码在无符号整数上执行位操作。我写的代码是基于这样的假设,即这些操作是在固定宽度且没有填充位的整数上进行的。例如,一个包含32位无符号整数的数组,每个整数都有32位可用。
我希望使我的代码更具可移植性,并且我专注于确保我符合C89标准(在这种情况下)。我遇到的问题之一是可能存在填充整数。从GMP手册中摘取的这个极端例子可以说明:
然而,在Cray向量系统上,可以注意到short和int始终存储在8字节中(并且使用sizeof指示),但只使用32或46位。通过传递例如8*sizeof(int)-INT_BIT,可以利用nails功能来解决这个问题。
我也在其他地方读到过这种填充方式。昨晚我实际上在 Stack Overflow 上读到了一篇帖子(请原谅,我没有链接,我将从记忆中引用类似的内容),如果你有一个具有60个可用位的双精度数,那么其他4个位可以用于填充,并且这些填充位可以用于某些内部目的,因此不能被修改。
假设我的代码在一个无符号整数大小为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?
对于变量d和e也是同样的问题。通过移位操作,我是否在操作填充位? 假设有32位,其中最高的2位用于填充,我期望看到下面的结果,但我想知道是否有类似的保证:
a: 2AAAAAAA
b: 15555555
c: 3FFFFFFF
d: 3FFFFFE0
e: 01FFFFFF

也就是说,填充位始终是最高有效位吗?还是它们可以是最低有效位?

编辑于2010年12月19日下午5点(美国东部时间):Christoph已经回答了我的问题。谢谢!
我之前也问过(上面)填充位是否总是最高有效位。这在C99标准的理由中有提到,答案是否定的。为了保险起见,我假设C89也是一样的。以下是C99理由中对于§6.2.6.2(整数类型的表示)的具体说明:

填充位在无符号整数类型中是可访问的。例如,假设一台机器使用一对16位的short(每个都有自己的符号位)组成一个32位的int,并且当在这个32位的int中使用时,忽略了较低short的符号位。然后,作为一个32位有符号int,存在一个填充位(在32位的中间),在确定32位有符号int的值时被忽略。但是,如果将这个32位项目视为32位无符号int,则该填充位对用户程序可见。C委员会被告知有一台机器是以这种方式工作的,这也是为什么在C99中添加填充位的原因之一。
脚注44和45提到奇偶校验位可能是填充位。委员会不知道是否有任何具有用户可访问奇偶校验位的机器。因此,委员会不知道是否有任何将奇偶校验位视为填充位的机器。
编辑于2010年12月28日下午3点(美国东部时间):我在几个月前的comp.lang.c上找到了一个有趣的讨论。

迪特马尔提出了一个我觉得很有意思的观点:

值得注意的是,填充位并不是陷阱表示存在的必要条件;不代表对象类型值的组合也可以。


1
最后的评论是错误的。通过简单的计数论证,填充位对于陷阱表示的存在是必要的。C语言仅允许无符号值使用纯二进制表示,并且仅允许3种可能的有符号值表示,当该值适合任一时,所有这些表示都与相应的无符号类型在表示上一致。 - R.. GitHub STOP HELPING ICE
R,我的 Dietmar 引用是在讨论后期提到的,如果不支持负零 -0,它可能会成为陷阱表示,并且按位补码运算符可能会导致这样的表示。我假设一个例子是 ~(int)0,其中有符号整数表示是一的补码,并且根据实现可能会导致陷阱表示。 - Anonymous Question Guy
尽管我现在才意识到,在标准中,符号位是单独提及的,与数值位不同,因此-0也需要有符号位。 - Anonymous Question Guy
1
@AQG:我同意你的评论。在非二进制补码实现中,只有一种可能的陷阱表示形式,不依赖于填充位。 - R.. GitHub STOP HELPING ICE
1个回答

11

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

整数填充可能会导致问题的情况是,如果您使用诸如 sizeof(int)*CHAR_BIT 这样的东西,然后尝试使用移位访问所有这些位。如果要实现可移植性,请仅使用(unsignedchar、固定大小的整数(C99 的一项新增功能)或通过比较 UINT_MAX 与 2 的幂来在预处理器中在编译时确定值位数,或者使用位运算在运行时确定。

编辑:

C90 标准根本没有提到整数填充,但据我所知,“无形”的前导或尾随整数填充位不应违反标准(我没有翻阅所有相关章节以确保这确实是这种情况)。如 C99 的解释所述,混合填充和值位可能会出现问题,否则,标准不需要更改。

关于“用户可访问”的含义:通过使用对 ((unsigned char *)&foo)[...] 的位运算,填充位在某种程度上是可访问的,以便您始终可以获取 foo 的任何位(包括填充)。但是,在修改填充位时要小心:结果不会更改整数的值,但可能会创建一个陷阱表示。在 C90 的情况下,这是隐含的未指定(根本没有提到),在 C99 的情况下,这是实现定义的。

然而,这并不是引用的架构设计的重点:所述架构通过两个16位整数表示32位整数。对于无符号类型,结果整数具有32个值位和32个精度位;对于有符号整数,结果整数仅具有31个值位和30个精度位:16位整数中的一个符号位被用作32位整数的符号位,另一个符号位被忽略,从而创建了一个由值位包围的填充位。现在,如果您将32位有符号整数作为无符号整数访问(这是明确允许且不违反C99别名规则),则填充位成为(用户可访问的)值位。


完全正确。这意味着示例代码很好(唯一的可移植性问题是假设 UINT_MAX0x3FFFFFFF)。 - caf
谢谢Christoph,我已将您的答案标记为正确。我假设您引用unsigned char是因为它不能有任何填充位。我知道这对于C99是正确的,您能否确认它适用于C89?此外,我想知道您对我最近的编辑有何看法。 C99的理由是“填充位在无符号整数类型中是用户可访问的。” “用户可访问”是否意味着我的程序可以以某种方式修改无符号整数中的填充位,这与C89有什么关系?它只被认为是未指定的吗?再次感谢。 - Anonymous Question Guy
嗨,Christoph,感谢您更新您的答案。我阅读了您的编辑,非常有帮助。 - Anonymous Question Guy
@AQG:唯一访问填充位的方式是将类型表示为 unsigned char [sizeof(type)]。如果您在 unsigned char 的值位中看不到某个位,则可以认为该位不存在。因此,从本质上讲,unsigned char “没有”填充位,即使它们存在于硬件上,程序也无法看到它们,因此“不存在”。 - R.. GitHub STOP HELPING ICE
@Christoph,我正在阅读C99规范,其中在6.5.7.4中指出:“E1 << E2的结果是将E1左移E2位”,没有任何地方说明移位仅发生在E1的对象表示的值位上。 - Kun
@Kun:继续阅读:如果E1具有无符号类型,则结果的值为E1×2^(E2),对结果类型可表示的最大值加一取模。如果E1具有带符号类型且非负值,并且E1×2^(E2)可以在结果类型中表示,则该值为结果;否则,行为未定义。 参见我的回答的第一句话。 - Christoph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接