C语言 - 将~0强制转换为无符号长整型

8

我曾看到过使用~0作为生成所有1位模式的一种低级按位表达式,然后用作掩码等用途。例如在K&R第45页:

/* getbits: get n bits from position p */
unsigned getbits(unsigned x, int p, int n)
{
    return (x >> (p+1-n)) & ~(~0 << n);
}

在我的机器上,(unsigned long) ~0 的计算结果为0x FF FF FF FF FF FF FF FF。这使我们能够轻松生成比int大的1掩码,非常方便。
然而,(unsigned long) ~0 不应该计算为0x 00 00 00 00 FF FF FF FF吗?没有任何后缀,0被视为整数常量,因此~0计算结果为0x FF FF FF FF。为什么将其转换为unsigned long不会简单地得到一个零填充值?我错过了什么?
编辑:在我的机器上,sizeof(int)sizeof(long)分别为4和8。

3
零填充值?在整数提升过程中,它用符号位值进行填充(即对于负填充为1),否则(long)-1将不等于-1L - Iłya Bursov
4
~0 会产生 (int)0xffffffff = -1。将其转换为无符号类型会产生唯一的无符号值,该值等于原始值对2^n取模,其中n是无符号位数。在您的情况下,n=64,等于 -1 模2^64的唯一64位值是 0xffffffffffffffff - Raymond Chen
3
Hermit,要轻松生成所有1的(unsigned long),请使用ULONG_MAX或者~0UL或者-1UL。避免使用_signed_常量来完成此任务。 - chux - Reinstate Monica
1
@Hermit 这实际上不是符号扩展。符号扩展的目的是使用更多的位来存储一个值,但保留原始值。当你将一个负值转换为无符号类型时,原始值并没有被保留。 - Support Ukraine
1
天啊,这段代码实际上是K&R的...为了避免未定义行为,请放下那本可怕的书!在C语言中,您不能将有符号和负数左移,这在K&R第二版出版之前就已广为人知。只是摆脱这本书,它会带来积极的伤害。 - Lundin
显示剩余4条评论
2个回答

14

在你的系统中,~0 是一个带有位模式 FF FF FF FFint,该模式表示十进制值为 -1。

当你转换为 unsigned long 时,你将一个整数类型转换为另一个整数类型。规则可以在C(草案)标准n1570的6.3.1.3中找到:

1 将具有整数类型的值转换为除了 _Bool 之外的其他整数类型时,如果该值可以由新类型表示,则该值不变。

2 否则,如果新类型是无符号的,则通过反复添加或减去可以在新类型中表示的最大值加上一来将该值转换,直到该值在新类型的范围内。60)

60) 这些规则描述的是数学值的算术,而不是给定表达式类型的值。

第一条规则不适用,因为 -1 不能表示为 unsigned long。第二条规则适用,即新值如下所示:

new-value = original-value + (ULONG_MAX + 1)

在你的情况下:

new-value = -1 + (ULONG_MAX + 1) = ULONG_MAX

因此,新值为ULONG_MAX,在您的系统上表示为FF FF FF FF FF FF FF FF


2
补充一些细节:在进行这些转换时,应该将方程式作为纯抽象数学来处理,而不考虑操作数的宽度。在6.3.1.3处有一个有帮助的脚注:“这些规则描述了数学值上的算术运算,而不是给定类型表达式的值”。也许为了完整起见,在回答中加入这个注释。 - Lundin

3

除了@SupportUkraine的出色回答,您还应该知道return (x >> (p+1-n)) & ~(~0 << n);会导致未定义的行为:

6.5.7 位移运算符

[...]

4 E1 << E2 的结果是将 E1 左移 E2 位;空出的位用零填充。如果 E1 具有无符号类型,则结果的值为 E1 × 2E2,并进行环绕。如果 E1 具有带符号类型和非负值,并且 E1 × 2E2 可以在结果类型中表示,则该值为结果;否则,行为未定义。

~0 的类型为 int,值为 -1,因此对于 n > 0~0 << n 表现未定义。您应该使用 ~0U << n,并且使用的 x 类型比 unsigned int 更大。对于所有无符号类型的 x,一个通用的表达式是 ~(0U * x) << n


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