Cómo configurar, borrar y alternar un solo bit

Resuelto JeffV asked hace 16 años • 27 respuestas

¿Cómo puedo configurar, borrar y alternar un poco?

JeffV avatar Sep 07 '08 07:09 JeffV
Aceptado

poniendo un poco

Utilice el operador OR bit a bit ( |) para establecer nel bit de numberen 1.

// Can be whatever unsigned integer type you want, but
// it's important to use the same type everywhere to avoid
// performance issues caused by mixing integer types.
typedef unsigned long Uint;

// In C++, this can be template.
// In C11, you can make it generic with _Generic, or with macros prior to C11.
inline Uint bit_set(Uint number, Uint n) {
    return number | ((Uint)1 << n);
}

Tenga en cuenta que es un comportamiento indefinido desplazarse más que el ancho de un archivo Uint. Lo mismo se aplica a todos los ejemplos restantes.

aclarando un poco

Utilice el operador AND bit a bit ( &) para establecer el ndécimo bit de numberen 0.

inline Uint bit_clear(Uint number, Uint n) {
    return number & ~((Uint)1 << n);
}

Debe invertir la cadena de bits con el operador NOT bit a bit ( ~) y luego AND.

alternando un poco

Utilice el operador XOR bit a bit ( ^) para alternar el nésimo bit de number.

inline Uint bit_toggle(Uint number, Uint n) {
    return number ^ ((Uint)1 << n);
}

comprobando un poco

No pediste esto, pero también podría agregarlo.

Para verificar un poco, desplace number nhacia la derecha, luego bit a bit Y:

// bool requires #include <stdbool.h> prior to C23
inline bool bit_check(Uint number, Uint n) {
    return (number >> n) & (Uint)1;
}

Cambiando el enésimo bit a x

Hay alternativas con peor codegen, pero la mejor manera es borrar el bit como en bit_cleary luego establecer el bit en valor, similar a bit_set.

inline Uint bit_set_to(Uint number, Uint n, bool x) {
    return (number & ~((Uint)1 << n)) | ((Uint)x << n);
}

Todas las soluciones han sido probadas para proporcionar codegen óptimo con GCC y clang. Consulte https://godbolt.org/z/Wfzh8xsjW .

Paige Ruten avatar Sep 07 '2008 00:09 Paige Ruten

Usando la biblioteca estándar de C++: std::bitset<N>.

O la versión Boostboost::dynamic_bitset : .

No es necesario que hagas el tuyo propio:

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<5> x;

    x[1] = 1;
    x[2] = 0;
    // Note x[0-4]  valid

    std::cout << x << std::endl;
}

./a.out

Producción:

00010

La versión Boost permite un conjunto de bits del tamaño del tiempo de ejecución en comparación con un conjunto de bits del tamaño del tiempo de compilación de una biblioteca estándar .

Martin York avatar Sep 18 '2008 00:09 Martin York

La otra opción es utilizar campos de bits:

struct bits {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct bits mybits;

define un campo de 3 bits (en realidad, son tres campos de 1 bit). Las operaciones de bits ahora se vuelven un poco (jaja) más simples:

Para configurar o borrar un poco:

mybits.b = 1;
mybits.c = 0;

Para alternar un poco:

mybits.a = !mybits.a;
mybits.b = ~mybits.b;
mybits.c ^= 1;  /* all work */

Comprobando un poco:

if (mybits.c)  //if mybits.c is non zero the next line below will execute

Esto sólo funciona con campos de bits de tamaño fijo. De lo contrario, tendrás que recurrir a las técnicas de manipulación de bits descritas en publicaciones anteriores.

Ferruccio avatar Sep 11 '2008 00:09 Ferruccio

Utilizo macros definidas en un archivo de encabezado para manejar la configuración y el borrado de bits:

/* a=target variable, b=bit number to act upon 0-n */
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
#define BIT_FLIP(a,b) ((a) ^= (1ULL<<(b)))
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b))))        // '!!' to make sure this returns 0 or 1

#define BITMASK_SET(x, mask) ((x) |= (mask))
#define BITMASK_CLEAR(x, mask) ((x) &= (~(mask)))
#define BITMASK_FLIP(x, mask) ((x) ^= (mask))
#define BITMASK_CHECK_ALL(x, mask) (!(~(x) & (mask)))
#define BITMASK_CHECK_ANY(x, mask) ((x) & (mask))
Steve Karg avatar Nov 04 '2008 22:11 Steve Karg

A veces vale la pena usar un enumpara nombrar los bits:

enum ThingFlags = {
  ThingMask  = 0x0000,
  ThingFlag0 = 1 << 0,
  ThingFlag1 = 1 << 1,
  ThingError = 1 << 8,
}

Luego use los nombres más adelante. es decir escribir

thingstate |= ThingFlag1;
thingstate &= ~ThingFlag0;
if (thing & ThingError) {...}

para configurar, borrar y probar. De esta manera ocultas los números mágicos del resto de tu código.

Aparte de eso, respaldo la solución de Paige Ruten .

De bitops.h de snip-c.zip :

/*
**  Bit set, clear, and test operations
**
**  public domain snippet by Bob Stout
*/

typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

Bueno, analicemos las cosas...

La expresión común con la que parece tener problemas en todos estos es "(1L << (posn))". Todo lo que esto hace es crear una máscara con un solo bit activado y que funcionará con cualquier tipo de entero. El argumento "posn" especifica la posición donde desea el bit. Si posn==0, entonces esta expresión se evaluará como:

0000 0000 0000 0000 0000 0000 0000 0001 binary.

Si posn==8, se evaluará como:

0000 0000 0000 0000 0000 0001 0000 0000 binary.

En otras palabras, simplemente crea un campo de 0 con un 1 en la posición especificada. La única parte complicada está en la macro BitClr() donde necesitamos establecer un único bit 0 en un campo de unos. Esto se logra utilizando el complemento a 1 de la misma expresión indicada por el operador de tilde (~).

Una vez creada la máscara, se aplica al argumento tal como usted sugiere, mediante el uso de los operadores bit a bit y (&), o (|) y xor (^). Dado que la máscara es de tipo long, las macros funcionarán igual de bien en char, short, int o long.

La conclusión es que se trata de una solución general a toda una clase de problemas. Por supuesto, es posible e incluso apropiado reescribir el equivalente de cualquiera de estas macros con valores de máscara explícitos cada vez que lo necesite, pero ¿por qué hacerlo? Recuerde, la sustitución de macros ocurre en el preprocesador y, por lo tanto, el código generado reflejará el hecho de que el compilador considera constantes los valores; es decir, es tan eficiente usar macros generalizadas como "reinventar la rueda" cada vez que necesite hacerlo. hacer manipulación de bits.

¿No estás convencido? Aquí hay un código de prueba: utilicé Watcom C con optimización completa y sin usar _cdecl para que el desmontaje resultante fuera lo más limpio posible:

----[ PRUEBA.C ]----------------------------------------- -----------------------

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

int bitmanip(int word)
{
      word = BitSet(word, 2);
      word = BitSet(word, 7);
      word = BitClr(word, 3);
      word = BitFlp(word, 9);
      return word;
}
----[ TEST.OUT (disassembled) ]-----------------------------------------------

Module: C:\BINK\tst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS

Segment: _TEXT  BYTE   00000008 bytes
 0000  0c 84             bitmanip_       or      al,84H    ; set bits 2 and 7
 0002  80 f4 02                          xor     ah,02H    ; flip bit 9 of EAX (bit 1 of AH)
 0005  24 f7                             and     al,0f7H
 0007  c3                                ret

No disassembly errors

----[ finis ]-----------------------------------------------------------------
yogeesh avatar Sep 17 '2008 02:09 yogeesh