Cómo configurar, borrar y alternar un solo bit
¿Cómo puedo configurar, borrar y alternar un poco?
poniendo un poco
Utilice el operador OR bit a bit ( |
) para establecer n
el bit de number
en 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 n
décimo bit de number
en 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
n
hacia 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_clear
y 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 .
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 .
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.
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))
A veces vale la pena usar un enum
para 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 ]-----------------------------------------------------------------