如何以C标准方式进行位表示?

tin*_*ast 28 c standards bit-manipulation bit

根据C标准,整数类型的值表示是实现定义的.因此,5可能不会像32位2的补码那样表示00000000000000000000000000000101-1正如11111111111111111111111111111111我们通常所假设的那样.因此,即使运营商~,<<并且>>是有严格规定的位模式,他们将合作上实现定义.我能找到的唯一定义的位模式是"§5.2.1/ 3所有位都设置为0的字节,称为空字符,应存在于基本执行字符集中;它用于终止字符串." .

所以我的问题是 - 是否有一种实现独立的方式将整数类型转换为位模式?

我们总是可以从一个空字符开始,并对它进行足够的位操作以使其达到所需的值,但我发现它太麻烦了.我也意识到几乎所有实现都将使用2的补码表示,但我想知道如何以纯C标准方式进行.我个人认为这个主题非常有趣,因为设备驱动程序编程的问题是所有直到日期编写的代码都采用特定的实现方式.

maf*_*fso 19

一般来说,这不是很难以适应大多数情况下不寻常的平台(如果你不想简单地假定8位char,2的补,没有空白,没有陷阱,和截断的无符号到符号的转换),标准主要是提供足够的保证(但是,检查某些实现细节的一些宏会有所帮助).

对于严格符合的程序可以观察到(在比特字段之外),5总是被编码为00...0101.这不一定是物理表示(无论这意味着什么),但可移植代码可以观察到什么.例如,内部使用格雷码的机器必须为按位运算符和移位模拟"纯二进制表示法".

对于带符号类型的负值,允许使用不同的编码,这会在重新解释为相应的无符号类型时导致不同的(但每种情况下定义良好)结果.例如,严格符合代码必须区分有符号整数(unsigned)n*(unsigned *)&n有符号整数n:它们对于没有填充位的二进制补码是相等的,但如果n是负数则对其他编码是不同的.

此外,填充比特可以存在,并且带符号的整数类型可以具有比其对应的无符号对应物更多的填充比特(但是不是相反的方式,从有符号到无符号的类型 - 惩罚总是有效的).sizeof不能用于获取非填充位的数量,因此例如,为了获得无符号值,只设置符号位(相应的有符号类型),必须使用以下内容:

#define TYPE_PUN(to, from, x) ( *(to *)&(from){(x)} )
unsigned sign_bit = TYPE_PUN(unsigned, int, INT_MIN) &
                    TYPE_PUN(unsigned, int, -1) & ~1u;
Run Code Online (Sandbox Code Playgroud)

(可能有更好的方式)而不是

unsigned sign_bit = 1u << sizeof sign_bit * CHAR_BIT - 1;
Run Code Online (Sandbox Code Playgroud)

因为这可能会移动超过宽度.(我不知道给出宽度的常量表达式,但是sign_bit从上面可以右移直到它为0来确定它,Gcc可以对它进行常数折叠.)填充位可以memcpy通过进入unsigned char数组进行检查,尽管它们可以可能看起来"抖动":两次读取相同的填充位可能会产生不同的结果.

如果你想要有符号整数(小端)的位模式(没有填充位):

int print_bits_u(unsigned n) {
    for(; n; n>>=1) {
        putchar(n&1 ? '1' : '0'); // n&1 never traps
    }
    return 0;
}

int print_bits(int n) {
    return print_bits_u(*(unsigned *)&n & INT_MAX);
    /* This masks padding bits if int has more of them than unsigned int.
     * Note that INT_MAX is promoted to unsigned int here. */
}

int print_bits_2scomp(int n) {
    return print_bits_u(n);
}
Run Code Online (Sandbox Code Playgroud)

print_bits根据所使用的表示(它给出原始位模式)print_bits_2scomp给出负数的不同结果,给出二进制补码表示(可能具有比a更大的宽度signed int,如果unsigned int具有更少的填充位).

在使用按位运算符时,必须注意不要生成陷阱表示,当从无符号运算符到有符号运算符时,请注意以下如何生成这些表达式(例如,*(int *)&sign_bit可以使用二进制补码-1 | 1进行陷阱,并且可以使用二进制补码进行陷阱) .

无符号到有符号整数转换(如果转换后的值在目标类型中无法表示)始终是实现定义的,我希望非2的补码机器更可能与通用定义不同,尽管从技术上讲,它也可能成为2的补充实现的问题.

从C11(n1570)6.2.6.2:

(1)对于除了以外的无符号整数类型unsigned char,对象表示的位应分为两组:值位和填充位(不需要后者中的任何一个).如果有N个值位,则每个位应表示12 N-1之间的2的不同幂,因此该类型的对象应能够使用纯二进制表示来表示02 N -1的值; 这应该被称为价值表示.任何填充位的值都未指定.

(2)对于有符号整数类型,对象表示的位应分为三组:值位,填充位和符号位.不需要任何填充位; signed char不得有任何填充位.应该只有一个符号位.作为值位的每个位应具有与相应无符号类型的对象表示中的相同位相同的值(如果在有符号类型中存在M个值位且在无符号类型中存在N,则M≤N).如果符号位为零,则不应影响结果值.如果符号位为1,则应以下列方式之一修改该值:

  • 符号位0的相应值被否定(符号和幅度);
  • 符号位的值为- (2 M)(二进制补码);
  • 符号位的值为- (2 M -1)(1'补码).

这些适用中的哪一个是实现定义的,将符号位1和所有值位0(对于前两个),或者符号位和所有值位1(对于1'补码)的值是否是陷阱表示或正常值.在符号和幅度以及1'补码的情况下,如果该表示是正常值,则称为负零.


ben*_*enj 5

为了增加mafso的优秀答案,有一部分ANSI C理由说明了这一点:

委员会已明确将C语言限制为二进制体系结构,理由是这种限制在任何情况下都是隐含的:

  • 位字段由多个位指定,没有提到"无效整数"表示.这种位字段唯一合理的编码是二进制.
  • printf的整数格式建议不提供"无效整数"值,这意味着任何按位操作的结果都会产生一个整数结果,该结果可以由printf打印.
  • 指定整数常量的所有方法 - 十进制,十六进制和八进制 - 指定整数值.没有定义独立于整数的方法来指定"位串常量".只有二进制编码才能在位串和整数值之间提供完整的一对一映射.

对二进制数字系统的限制排除了格雷码这样的好奇心,并使得无符号类型的位运算符的算术定义成为可能.

标准的相关部分可能是这个引用:

3.1.2.5类型

[...]

char类型,有符号和无符号整数类型以及枚举类型统称为整数类型.整数类型的表示应使用纯二进制计算系统定义值.