如何在C语言中构建一个比特掩码(bit mask), 该掩码有m
个已置位的比特,之前有k
个未置位比特,之后还有n
个未置位比特:
00..0 11..1 00..0
k m n
例如,k=1,m=4,n=3会产生位掩码:01111000
如何在C语言中构建一个比特掩码(bit mask), 该掩码有m
个已置位的比特,之前有k
个未置位比特,之后还有n
个未置位比特:
00..0 11..1 00..0
k m n
例如,k=1,m=4,n=3会产生位掩码:01111000
您可以进行以下操作:
~(~0 << m) << n
~(~0 << m)
出现在《C程序设计语言第二版》的第2.9段"按位运算符"中。它也出现在《编程实践》的第7.5段"空间效率"中,这两本书都是由Brian Kernighan和Dennis Ritchie合著的。 - Alessandro Jacopson所以,你要求的是在 k 个重置位之前和 n 个重置位之后有 m 个设置位吗?我们可以忽略 k ,因为它在很大程度上会受到整数类型的选择的限制。
mask = ((1 << m) - 1) << n;
我喜欢这两种解决方案。以下是我想到的另一种方式(可能并不更好):
((~((unsigned int)0) << k) >> (k + n)) << n
编辑:
我的上一个版本中存在一个错误(没有使用unsigned int强制转换)。问题在于~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;
}
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
bzhi
和shlx
的延迟为1个时钟周期,吞吐量为每个周期2次。在AMD Ryzen上,这两条指令的吞吐量甚至达到每个周期4次。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
对于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
。
#define BITS(p,q) ...
其中p = m + n - 1且q = n,p >= q。 - user246672k
。只使用m
和n
指定要设置的位范围更容易。 - Nubcake