c - 从无符号int提取位的函数

标签 c bit-manipulation bit bit-shift

编写一个名为bitpat_get()的函数来提取指定的位集。让它接受三个参数:第一个anunsigned int,第二个是整数起始位,第三个是位计数。按照最左边的位从0开始的惯例,从第一个参数中提取指定数量的位并返回结果。所以这个电话
bitpat_get(x, 0, 3)
从中提取最左边的三个位。电话
bitpat_get(x, 3, 5)
从左边的第四位开始提取五位。
我真的不知道作者提取位是什么意思,所以我几乎可以肯定我的代码是错误的,无论它返回什么都不是预期的返回值。不过,我还是会贴出来的:

#include <stdio.h>

unsigned int bitpat_get(unsigned int from, int start, int n);

int main(void)
{
    unsigned int x = 0xe1f4;

    printf("%x\n", bitpat_get(x, 0, 3));
    printf("%x\n", bitpat_get(x, 3, 5));
}

unsigned int bitpat_get(unsigned int from, int start, int n)
{
    unsigned int result = from;
    int bits;

    for (bits = 0; (from >> bits) != 0; ++bits)
        continue;

    unsigned int mask = (((1U << n) - 1) << (bits - n - start));

    result = from ^ mask;

    return result;
}

输出:
1f4
fef4

最佳答案

我真的不知道作者所说的提取比特是什么意思。
我们先解决这个问题。假设您有一个16位无符号整数,则位位置为:

                     1 1 1 1 1 1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

所以表达式应该给出从偏移量0开始的三位,或者说bitpat_get(x, 0, 3)。类似地,abc将在偏移量3处给您5位,或者bitpat_get(x, 3, 5)
这应该足以理解你需要做什么。
就你需要做什么来实现这个目标而言,这是一个两步操作。第一种方法是实际地将位右移(a),使所需的位处于最右边的位置。这取决于三条信息:
defgh的位宽度;
要提取的偏移;以及
要提取的位数。
要移动的距离是unsigned int。对于第一种情况,这将是bitWidth - offset - bitsNeeded并且您可以看到将位向右移动13将把所需的位放在最右边的部分:
                     1 1 1 1 1 1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|0|0|a|b|c|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对于第二种情况,向右移动16 - 0 - 3 = 13可以:
                     1 1 1 1 1 1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|a|b|c|d|e|f|g|h|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

第二步是屏蔽掉左边那些你根本不需要的部分。我们先做第二个案子,因为那有实际效果。
掩模基本上是一系列的一位在右边,可以通过从零开始,左移一位来获得你需要的每一位位置。对于需要5位的情况,序列将是二进制的16 - 3 - 5 = 80111111111111111。按位并使用该值将给出:
                     1 1 1 1 1 1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|a|b|c|d|e|f|g|h| <- value
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|1|1|1|1|1| <- "and" with
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|d|e|f|g|h| <- gives
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对于需要三位的第一种情况,掩码将是二进制的,因此不会影响原始值,因为最左边的所有位都已经为零。
注意,您不需要在循环中执行此操作,因为正如您的代码所示,您可以使用单个表达式计算它:
unsigned mask = (1U << n) - 1U;

从你发布的代码来看,我发现了一些问题。
首先,我认为您的111部分是为了找出2n - 1的位宽度,这取决于您以后对该值的使用。但是,您根据传入的值计算它,这是不正确的。你应该建立在一个比特模式的基础上,最左边的比特是一个。
换言之,想想如果传入的值是3(二进制for..continue),那么当前循环将执行什么操作-位宽度将计算为2,因为只经过两次移位,最终将为零。所以,更好的方法是:
unsigned testVal = ~0U; // all one bits
for (bits = 0; testVal != 0; ++bits, testVal = testVal >> 1)
    ;

其次是你的面具计算。您的代码设置为就地提取位,这意味着您只需将它们周围的所有其他位设置为零。最好把它们移到右边提取(a)。
第三,您应该知道unsigned int是异或操作,如果您使用一个所有位的掩码,它将反转这些位,而不是按原样提取它们。你要找的接线员是11
举例来说,将xor运算符与^一起使用将给出:
                     1 1 1 1 1 1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|1|0|1|0|1| <- value (21)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|1|1|1|1|1| <- "xor" with
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|0|0|0|0|0|0|0|0|0|0|1|0|1|0| <- `01010` (10): NOT the correct `10101`
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

这么说之后,我会把函数写成这样:
unsigned bitpat_get(unsigned from, unsigned start, unsigned count) {
    // Only need calculate this once, first time it's called.

    static unsigned bitWidth = 0;
    if (bitWidth == 0) {
        unsigned testVal = ~0U;
        while (testVal != 0) {
            bitWidth++;
            testVal = testVal >> 1;
        }
    }

    // Get the value you need to shift by.

    unsigned shiftCount = bitWidth - start - count;

    // Use this line if in-place bits needed.
    // unsigned mask = ((1U << count) - 1U) << shiftCount;

    // Or use these two lines if you need it on the right.
    from = from >> shiftCount;
    unsigned mask = (1U << count) - 1U;

    // Mask and return the bits.

    unsigned result = from & mask;

    return result;
}

唯一棘手的地方是使用静态&,所以只需要计算一次。这只是一个优化,以加快事情在随后的呼吁。如果您不想这样做(例如,如果您不熟悉这些概念,或者如果第一次可能同时从多个线程调用此函数,从而导致数据竞争),只需将其替换为:
unsigned bitWidth = 0;
unsigned testVal = ~0U;
while (testVal != 0) {
    bitWidth++;
    testVal = testVal >> 1;
}

(a)这是基于经验。你可能希望他们到位,但是,在我漫长而(偶尔)辉煌的职业生涯中,我总是发现把他们安排在轮岗部门更有用。例如,如果位11-13是某种形式的整数值,则将它们移到最右边实际上会得到值bitpat_get(21, 11, 5),而不是集合bitWidth中的值。
可能不是这样,所以我提供的代码涵盖了这两种情况,如果您只是注释掉替代情况。

关于c - 从无符号int提取位的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49354105/

相关文章:

c - 如何使宏的第一次调用不同于所有后续调用?

c - 如何使用 scanf 函数从 ls -l 作为参数的结果中解析两个特定信息?

c - 如何为 C 中声明之外的字符串赋值?总线错误/段错误

c - 随机数种子生成器 : Using System On Time

python - 给定任意长度的整数,Python 中按位非的确切定义是什么?

c - 如果我有真值表,是否有办法确定该真值表所需的按位表达式?

python - 写一个位翻转算法

c# - 可以防止非位掩码字段上的意外按位运算符吗?

在C中递归地将8位数字转换为8位二进制

c - 如何从C中的数字中提取特定位?