C中的位掩码

Posted

技术标签:

【中文标题】C中的位掩码【英文标题】:Bit mask in C 【发布时间】:2010-09-23 21:48:17 【问题描述】:

m设置位前面是k未设置位,然后是n未设置位,在C中构造位掩码的最佳方法是什么:

00..0 11..1 00..0
  k     m     n

例如,k=1、m=4、n=3 会产生位掩码:

01111000

【问题讨论】:

对于许多诸如此类的小技巧的答案,一个非常好的在线资源是Bit Twiddling Hacks。 通常,位掩码宏定义在 inclusive 位索引上,例如 #define BITS(p,q) ... 其中 p = m + n - 1 和 q = n, p >= q Hacker's Delight 更全面(1.8 千页)而且很棒。 @grigy 我真的不明白为什么你需要在这里有k。仅使用 mn 指定要设置的位范围更容易。 【参考方案1】:

虽然最佳答案简单有效,但它们没有为n=0m=31 的情况设置 MSB:

~(~0 << 31) << 0 = 0111 1111 1111 1111 1111 1111 1111 1111‬

((1 << 31)-1) << 0 = 0111 1111 1111 1111 1111 1111 1111 1111‬

我对 32 位无符号字的建议如下所示:

unsigned int create_mask(unsigned int n,unsigned int m) 
  // 0 <= start_bit, end_bit <= 31
  assert(n >=0 && m<=31);
  return (m - n == 31 ? ~0: ((1 << (m-n)+1)-1) << n);

这实际上得到了[m,n](闭合区间)范围内的位,因此create_mask(0,0) 将返回第一个位(位0)的掩码,create_mask(4,6) 返回位4 到6 的掩码,即... 00111 0000 .

【讨论】:

【参考方案2】:

(仅限)对于在支持 BMI2 的 x86 系统(Intel Haswell 或更新版本、AMD Excavator 或更新版本)上更高效的解决方案感兴趣的人:

mask = _bzhi_u32(-1,m)<<n;

bzhi 指令将从指定位位置开始的高位归零。 _bzhi_u32 内部函数编译为此指令。测试代码:

#include <stdio.h>
#include <x86intrin.h>
/*  gcc -O3 -Wall -m64 -march=haswell bitmsk_mn.c   */

unsigned int bitmsk(unsigned int m, unsigned int n)

    return _bzhi_u32(-1,m)<<n;


int main() 
    int k = bitmsk(7,13);
    printf("k= %08X\n",k);
    return 0;

输出:

$./a.out
k= 000FE000

代码片段_bzhi_u32(-1,m)&lt;&lt;n编译成三条指令

movl    $-1, %edx
bzhi    %edi, %edx, %edi
shlx    %esi, %edi, %eax

比@Jonathan Leffler 的代码少一条指令 和@Darius Bacon。 在 Intel Haswell 处理器或更新的处理器上,bzhishlx 都有 1 个周期的延迟和 每个周期的吞吐量为 2。在 AMD Ryzen 上,这两条指令的吞吐量甚至达到了每周期 4 条。

【讨论】:

【参考方案3】:

我喜欢这两种解决方案。这是我想到的另一种方式(可能不是更好)。

((~((unsigned int)0) << k) >> (k + n)) << n

编辑: 我以前的版本中有一个错误(它没有 unsigned int 强制转换)。问题是~0 &gt;&gt; n在前面加了1而不是0。

是的,这种方法有一个很大的缺点。它假定您知道默认整数类型的位数,或者换句话说,它假定您确实知道 k,而其他解决方案与 k 无关。这使得我的版本不那么便携,或者至少更难移植。 (它还使用了 3 次移位,以及加法和按位否定运算符,这是两个额外的操作。)

因此,您最好使用其他示例之一。

这是一个由 Jonathan Leffler 完成的小测试应用程序,用于比较和验证不同解决方案的输出:

#include <stdio.h>
#include <limits.h>

enum  ULONG_BITS = (sizeof(unsigned long) * CHAR_BIT) ;

static unsigned long set_mask_1(int k, int m, int n)

    return ~(~0 << m) << n;


static unsigned long set_mask_2(int k, int m, int n)

    return ((1 << m) - 1) << n;


static unsigned long set_mask_3(int k, int m, int n)

    return ((~((unsigned long)0) << k) >> (k + n)) << n;


static int test_cases[][2] =

     1, 0 ,
     1, 1 ,
     1, 2 ,
     1, 3 ,
     2, 1 ,
     2, 2 ,
     2, 3 ,
     3, 4 ,
     3, 5 ,
;

int main(void)

    size_t i;
    for (i = 0; i < 9; i++)
    
        int m = test_cases[i][0];
        int n = test_cases[i][1];
        int k = ULONG_BITS - (m + n);
        printf("%d/%d/%d = 0x%08lX = 0x%08lX = 0x%08lX\n", k, m, n,
               set_mask_1(k, m, n),
               set_mask_2(k, m, n),
               set_mask_3(k, m, n));
    
    return 0;

【讨论】:

假设这个答案可以工作,与其他两个相比明显的缺点是存在第三班操作,这使得它更耗时。 另一个问题是它使用了其他两个解决方案可以忽略的参数k(虽然它不使用m,所以它仍然只使用三个参数中的两个)。 确实有一个错误,我现在修复了它并添加了其他解决方案更可取的评论。我还没有完全删除它,也许有人可以从我的错误中吸取教训,丢失你漂亮的测试代码会很遗憾:)。 您应该能够使用“0U”来表示无符号零,或者使用“0UL”来表示无符号长整数,而不是强制转换。我同意保留您的答案 - 以及您所做的编辑。 将此设为宏或内联函数,编译器将在编译时生成常量而不是代码。【参考方案4】:

所以,您要求 m 个设置位,以 k 个复位位为前缀,然后是 n 个复位位?我们可以忽略 k,因为它在很大程度上会受到整数类型选择的限制。

mask = ((1 << m) - 1) << n;

【讨论】:

它们都有效,但我发现乔纳森的答案更简单、更清晰。大流士的回答对我来说有点太倒退了。 Robert,我喜欢位掩码的 ~0 习语,因为它不依赖于 2 的补码,并且在这个意义上更简单,但它确实不太为人所知。尽我所能来改变它! @Darius:如果您使用无符号算术/类型,就像在这些情况下应该使用的那样,2 的补码、1 的补码和符号幅度算术之间的区别是否无关紧要? @Darius,你不应该首先对有符号类型执行按位算术,如果你这样做了,你的解决方案每次都会调用未定义的行为! 是否未定义?我手头没有规范,但我认为它是实现定义的,即允许编译器按照他的意愿进行操作,但他必须始终以相同的方式进行操作。因此,当您知道(编译器的)处理方式时,您可以依赖它。【参考方案5】:

~(~0

【讨论】:

这很漂亮。但是,最好对这一行进行注释,以便 -next- 程序员处理它。 如果这被编码为一个函数(@quinmar 的答案中的 set_mask_n 函数),会有一行注释说明函数的作用(并且没有参数“k”),并且用户该函数的名称将作为文档。作为一段代码中的随机表达式,它无疑是坏的! 而且,我会加快(非常缓慢地)添加,如果它在一些代码中显示为未注释的表达式,我的解决方案将同样难以理解。 ~(~0 &lt;&lt; m) 位于 Brian Kernighan 和 Dennis Ritchie 的“C 编程语言,第二版”的第 2.9 段“位运算符”中。它也在 Brian W. Kernighan 和 Rob Pike 的“编程实践”的第 7.5 段“空间效率”中。 这种方法不能创建一个包含最长无符号整数类型最高位的掩码,即通常用integer overflow in preprocessor expression之类的警告表示。

以上是关于C中的位掩码的主要内容,如果未能解决你的问题,请参考以下文章

Postgres 中的位掩码

位掩码 - C中的按位运算

以下程序中的位掩码用法来自 Programming Pearls

C++ 中有趣的位掩码谜题

Java位运算在程序设计中的使用:位掩码(BitMask)

Java位运算在程序设计中的使用:位掩码(BitMask)