web-dev-qa-db-fra.com

Bit masque en C

Quel est le meilleur moyen de construire un masque de bits en C avec m bits définis précédés de k bits non définis et suivis de n bits non définis:

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

Par exemple, k = 1, m = 4, n = 3 donnerait le masque de bits:

01111000
21
grigy

~ (~ 0 << m) << n

41
Darius Bacon

Vous demandez donc m bits définis, précédés de k bits de réinitialisation et suivis de n bits de réinitialisation? On peut ignorer k car il sera largement contraint par le choix du type entier.

mask = ((1 << m) - 1) << n;
29
Jonathan Leffler

J'aime les deux solutions. Voici une autre façon qui me vient à l’esprit (probablement pas mieux).

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

EDIT: Il y avait un bug dans ma version précédente (c'était sans la distribution non signée). Le problème était que ~0 >> n ajoute des 1 à l'avant et non des 0.

Et oui cette approche a un gros inconvénient; cela suppose que vous connaissiez le nombre de bits du type entier par défaut ou, en d'autres termes, que vous connaissiez réellement k, alors que les autres solutions sont indépendantes de k. Cela rend ma version moins portable, ou du moins plus difficile à porter. (Il utilise également 3 décalages, ainsi que l’opérateur addition et un opérateur de négation au niveau du bit, ce qui correspond à deux opérations supplémentaires.)

Vous feriez donc mieux d'utiliser l'un des autres exemples.

Voici une petite application de test, réalisée par Jonathan Leffler, permettant de comparer et de vérifier le résultat des différentes solutions:

#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;
}
5
quinmars

(Uniquement) Pour ceux qui sont intéressés par une solution légèrement plus efficace sur les systèmes x86 prenant en charge BMI2 (Intel Haswell ou plus récent, AMD Excavator ou plus récente):

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

L'instruction bzhi met à zéro les bits hauts commençant par la position de bit spécifiée. Le _bzhi_u32 intrinsèque est compilé avec cette instruction. Code de test:

#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;
}

Sortie:

$./a.out
k= 000FE000

Le fragment de code _bzhi_u32(-1,m)<<n est compilé en trois instructions.

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

Ce qui correspond à une instruction de moins que les codes de @ Jonathan Leffler et @Darius Bacon . Sur les processeurs Intel Haswell ou plus récents, bzhi et shlx ont une latence de 1 cycle et un débit de 2 par cycle. Sur AMD Ryzen, ces deux instructions ont même un débit de 4 par cycle.

0
wim

Bien que les principales réponses soient simples et efficaces, elles ne définissent pas le MSB pour le cas où n=0 et 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‬

Ma suggestion pour un mot 32 bits non signé (qui est laid et a une branche) ressemble à ceci:

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);
}

Cela obtient en fait les bits dans la plage [m,n] (intervalle fermé), de sorte que create_mask(0,0) renvoie un masque pour le premier bit (bit 0) et create_mask(4,6) renvoie un masque pour les bits 4 à 6, i.e ... 00111 0000.

0
Nubcake