Подтвердить что ты не робот

Бит-маска в C

Каков наилучший способ построения битовой маски в C с m битами набора, которым предшествует k unset bits, а затем n unset bits:

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

Например, k = 1, m = 4, n = 3 приведет к битовой маске:

01111000
4b9b3361

Ответ 1

~ (~ 0 < m) < п

Ответ 2

Итак, вы запрашиваете m битов с префиксом k reset bits, а затем n reset bits? Мы можем игнорировать k, поскольку он будет в значительной степени ограничен выбором целочисленного типа.

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

Ответ 3

Мне нравятся оба решения. Вот еще один способ, который приходит мне на ум (возможно, не лучше).

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

EDIT: В моей предыдущей версии произошла ошибка (это было без трансляции без знака). Проблема заключалась в том, что ~0 >> n добавляет 1s спереди, а не 0s.

И да, этот подход имеет один большой недостаток; он предполагает, что вы знаете количество бит целочисленного типа по умолчанию или, другими словами, оно предполагает, что вы действительно знаете k, тогда как другие решения не зависят от k. Это делает мою версию менее переносной или, по крайней мере, труднее переносить. (Он также использует 3 смены и добавление и оператор побитового отрицания, что является двумя дополнительными операциями.)

Таким образом, вам лучше использовать один из других примеров.

Вот небольшое тестовое приложение, сделанное Джонатаном Леффлером, для сравнения и проверки вывода различных решений:

#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

В то время как верхние ответы просты и эффективны, они не устанавливают MSB для случая, когда 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
  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

(только) Для тех, кто заинтересован в немного более эффективном решении на системах x86 с поддержкой BMI2 (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)<<n компилируется в три команды

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

Что на одну инструкцию меньше, чем коды @Jonathan Leffler и @Darius Bacon. На процессорах Intel Haswell или новее оба bzhi и shlx имеют задержку в 1 цикл и пропускная способность 2 за цикл. На AMD Ryzen эти две инструкции имеют пропускную способность 4 за цикл.