需要帮助理解 K&R C 第 2 章中的“getbits()”方法

Posted

技术标签:

【中文标题】需要帮助理解 K&R C 第 2 章中的“getbits()”方法【英文标题】:Need help understanding "getbits()" method in Chapter 2 of K&R C 【发布时间】:2010-09-16 21:23:59 【问题描述】:

在第 2 章,关于位运算符的部分(第 2.9 节)中,我无法理解其中一种示例方法的工作原理。

提供的方法如下:

unsigned int getbits(unsigned int x, int p, int n) 
    return (x >> (p + 1 - n)) & ~(~0 << n);

这个想法是,对于给定的数字 x,它将返回从位置 p 开始的 n 位,从右数(最右边的位是位置 0)。给定以下main() 方法:

int main(void) 
    int x = 0xF994, p = 4, n = 3;
    int z = getbits(x, p, n);
    printf("getbits(%u (%x), %d, %d) = %u (%X)\n", x, x, p, n, z, z);

    return 0;

输出是:

getbits(63892 (f994), 4, 3) = 5 (5)

我得到了其中的一部分,但在“大局”方面遇到了麻烦,主要是因为我不理解的部分(不是双关语)。

我特别有问题的部分是补充部分:~(~0 &lt;&lt; n)。我想我得到了第一部分,处理 x;这是我正在努力解决的部分(然后是掩码)——以及它们是如何结合在一起来实际检索这些位的。 (我已经验证了它正在做的事情,包括代码和使用 calc.exe 检查我的结果——感谢上帝,它有一个二进制视图!)

有什么帮助吗?

【问题讨论】:

【参考方案1】:

让我们在示例中使用 16 位。在这种情况下,~0 等于

1111111111111111

当我们左移 n 位(在你的情况下为 3)时,我们得到:

1111111111111000

因为左边的1s 被丢弃,而右边的0s 被输入。然后重新补充它给出:

0000000000000111

所以这只是在数字的最不重要部分获得n 1 位的聪明方法。

您描述的“x 位”已将给定数字 (f994 = 1111 1001 1001 0100) 移动得足够远,因此最低有效 3 位是您想要的。在此示例中,您请求的输入位在那里,所有其他输入位都标记为.,因为它们对最终结果并不重要:

ff94             ...........101..  # original number
>> p+1-n     [2] .............101  # shift desired bits to right
& ~(~0 << n) [7] 0000000000000101  # clear all the other (left) bits

如您所见,您现在在最右边的位位置拥有相关位。

【讨论】:

我本来打算建议右移以保存指令,但我想,~(~0 &lt;&lt; n) 可以优雅地让您忽略正在使用的字长... 我有些不明白,我哪里错了?整数a = 0;所以 printf("%d", a) 给出 0。现在,a = ~a 所以 printf("%d", a) 给出 -1,为什么? @Anatoly:这是因为~ 将所有位反转为 1,并且在二进制补码编码中为 -1。 很好的答案。我终于明白这个例子了! :)【参考方案2】:

使用示例: int x = 0xF994,p = 4,n = 3; int z = getbits(x, p, n);

并专注于这组操作 ~(~0

对于任何位集(10010011 等),您想要生成一个“掩码”,它只提取您想要查看的位。所以10010011或0x03,我对xxxxx011感兴趣。将提取该集合的掩码是什么? 00000111 现在我想独立于 sizeof int,我会让机器完成工作,即从 0 开始对于字节机器它是 0x00 对于字机器它是 0x0000 等。64 位机器将由 64 位或 0x0000000000000000 表示 现在应用 "not" (~0) 并得到 11111111 将 ( 和“不”那个并得到 00000111 所以 10010011 & 00000111 = 00000011 你还记得布尔运算的工作原理吗?

【讨论】:

@jim:嘿,不要敲你帖子的准确性。内容方面的工作比其他两个要多,最好使用代码块并对齐更改。这些帖子使用 wiki 标记,并且一个 tute 页面链接到“?”答案框上方。我必须阅读它两次才能检查它。【参考方案3】:

~(~0 &lt;&lt; n) 创建一个掩码,该掩码将打开最右边的n 位。

0
   0000000000000000
~0
   1111111111111111
~0 << 4
   1111111111110000
~(~0 << 4)
   0000000000001111

将结果与其他内容进行与运算将返回 n 位中的内容。

编辑:我想指出这个我一直在使用的程序员计算器:AnalogX PCalc。

【讨论】:

【参考方案4】:

我会说最好的办法是手工解决问题,这样你就会明白它是如何工作的。

这是我使用 8 位无符号整数所做的。

    我们的数字是 75,我们想要从位置 6 开始的 4 位。 该函数的调用将是 getbits(75,6,4);

    二进制的75是0100 1011

    因此,我们创建了一个 4 位长的掩码,从最低位开始,这样就完成了。

~0 = 1111 1111 ~ = 0000 1111

好的,我们得到了我们的面具。

    现在,我们将数字中想要的位推入最低位,因此 我们将二进制 75 移动 6+1-4=3。

0100 1011 >>3 0000 1001

现在我们有了一个掩码,其中包含正确的低位位数和我们想要的低位原始位数中的位数。

    所以我们和他们
0000 1001 & 0000 1111 ============= 0000 1001

所以答案是十进制的 9。

注意: 高阶半字节恰好全为零,在这种情况下使屏蔽变得多余,但根据我们开始时的数字值,它可能是任何值。

【讨论】:

【参考方案5】:

还没有人提到它,但在 ANSI C 中 ~0 &lt;&lt; n 会导致未定义的行为。

这是因为~0 是一个负数,左移负数是未定义的。

参考:C11 6.5.7/4(早期版本有类似文字)

E1 &lt;&lt; E2 的结果是E1 左移E2 位位置;空出的位用零填充。 [...] 如果 E1 有一个签名 类型和非负值,而E1 × 2E2 在结果类型中是可表示的,那么就是结果值;否则,行为未定义。

在 K&R C 中,此代码将依赖于 K&R 开发的特定系统类别,当执行有符号数的左移时,天真地将 1 位左移(并且此代码还依赖于 2 的补码表示),但其他一些系统不共享这些属性,因此 C 标准化过程没有定义这种行为。

所以这个例子真的只是作为一个历史好奇而有趣,它不应该在 1989 年以来的任何实际代码中使用(如果不是更早的话)。

【讨论】:

【参考方案6】:

ANSI C ~0 &gt;&gt; n 中导致未定义的行为

//关于左移导致问题的帖子是错误的。

无符号字符 m,l;

m = ~0 >> 4;正在产生 255 并且它等于 ~0 但是,

m = ~0; l = m >> 4;正在产生与以下相同的正确值 15:

m = 255 >> 4;

左移负数~0 &lt;&lt;无论如何都没有问题

【讨论】:

以上是关于需要帮助理解 K&R C 第 2 章中的“getbits()”方法的主要内容,如果未能解决你的问题,请参考以下文章

这个类型结构定义的指针是啥意思(在C中)?

K&R C while 循环省略大括号

快速排序示例中的错误(K&R C 书)?

在 K&R 中找到的 C 问题中的二叉树实现

小B的询问

小B的询问 莫队分块