一般来说,在大多数情况下,适应不寻常的平台并不难(如果您不想简单地假设 8 位 char、2 的补码、无填充、无陷阱,并截断无符号到有符号的转换),该标准大多提供了足够的保证(不过,检查某些实现细节的一些宏会有所帮助)。
就严格符合程序可以观察到的(位域之外)而言,5 始终编码为00...0101。这不一定是物理表示(无论这应该意味着什么),而是可移植代码可以观察到的。例如,在内部使用格雷码的机器必须为按位运算符和移位模拟“纯二进制符号”。
对于有符号类型的负值,允许使用不同的编码,当重新解释为对应的无符号类型时,这会导致不同的(但对每种情况都有明确定义)的结果。例如,对于有符号整数n,严格符合代码必须区分(unsigned)n 和*(unsigned *)&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;
(可能有更好的方法)而不是
unsigned sign_bit = 1u << sizeof sign_bit * CHAR_BIT - 1;
因为这可能会移动超过宽度。 (我不知道给出宽度的常量表达式,但是上面的 sign_bit 可以右移直到它为 0 来确定它,Gcc 可以对其进行常量折叠。)填充位可以通过memcpying 检查进入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);
}
print_bits 对负数给出不同的结果,具体取决于所使用的表示形式(它给出原始位模式),print_bits_2scomp 给出二进制补码表示(如果@987654338,宽度可能比signed int 的宽度更大) @ 的填充位更少)。
在使用按位运算符以及从无符号到有符号的类型双关语时,必须注意不要生成陷阱表示,请参阅下面如何可能生成这些表示(例如,*(int *)&sign_bit 可以使用二进制补码进行陷阱,而 @ 987654340@可以用反码进行陷印)。
无符号到有符号整数转换(如果转换后的值在目标类型中不可表示)始终是实现定义的,我希望非 2 的补码机器更有可能不同于常见的定义,尽管在技术上,它也可能成为 2 的补码实现的问题。
从 C11 (n1570) 6.2.6.2:
(1) 对于unsigned char以外的无符号整数类型,对象表示的位应分为两组:值位和填充位(后者不需要任何一个)。如果有 N 个值位,每个位应代表 1 和 2N-1之间的 2 的不同幂>,以便该类型的对象能够使用纯二进制表示表示从 0 到 2N-1 的值;这应称为值表示。未指定任何填充位的值。
(2) 对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要任何填充位; signed char 不应有任何填充位。应该有一个符号位。作为值位的每个位都应与相应无符号类型的对象表示中的相同位具有相同的值(如果有符号中存在 M 个值位
type 和 N 在无符号类型,然后 M≤N )。如果符号位为零,则不应影响结果值。如果符号位为1,则按以下方式之一修改该值:
- 符号位为0的对应值取反(符号和幅度);
- 符号位的值是 -(2M)(二的补码);
- 符号位的值是 -(2M-1)(ones'补码)。
这些应用中的哪一个是实现定义的,无论符号位为 1 且所有值位为零(对于前两个),还是符号位和所有值位为 1(对于一个补码)的值是陷阱表示或正常值。在符号和幅度以及反码的情况下,如果这种表示是正常值,则称为负零。