代码之家  ›  专栏  ›  技术社区  ›  grigy

C中的位掩码

  •  21
  • grigy  · 技术社区  · 16 年前

    用C语言构造位掩码的最佳方法是什么 m 设置前导位 k 复位位,然后 n 未设置位:

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

    例如,k=1,m=4,n=3将导致位屏蔽:

    01111000
    
    5 回复  |  直到 8 年前
        1
  •  41
  •   Darius Bacon    16 年前

    ~(~0<<m)<<n

        2
  •  29
  •   Jonathan Leffler    16 年前

    那么,您要的是M集合位,前缀是K重置位,后面是N重置位?我们可以忽略k,因为它在很大程度上会受到整数类型选择的限制。

    mask = ((1 << m) - 1) << n;
    
        3
  •  5
  •   Jonathan Leffler    16 年前

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

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

    编辑: 我以前的版本中有一个bug(它没有unsigned int cast)。问题是 ~0 >> 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;
    }
    
        4
  •  0
  •   Nubcake    8 年前

    虽然最上面的答案简单有效,但在以下情况下,它们不会设置最高位: n=0 m=31 :

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

    ((1 << 31)-1) << 0 =爱 0111 1111 1111 1111 1111 1111 1111 1111 1111_

    我建议使用一个32位无符号单词(它很难看,有一个分支),如下所示:

    unsigned int create_mask(unsigned int n,unsigned int m) {
      // 0 <= start_bit, end_bit <= 31
      return (m - n == 31 ? 0xFFFFFFFF : ((1 << (m-n)+1)-1) << n);
    }
    

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

        5
  •  0
  •   wim    8 年前

    (仅适用于那些对具有bmi2支持的x86系统(Intel Haswell或更新版本、AMD挖掘机或更新版本)稍微更高效的解决方案感兴趣的用户:

    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)<<n 编译成三个指令

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

    比代码少一条指令 @Jonathan Leffler @Darius Bacon . 在Intel Haswell处理器或更新版本上,两者都是 布芝 shlx 有1个周期的延迟和 每周期2次的吞吐量。在AMD Ryzen上,这两个指令甚至每周期有4个吞吐量。