Come si imposta, cancella e si attiva / disattiva un singolo bit?


Risposte:


3600

Impostazione un po '

Utilizzare l'operatore OR bit a bit ( |) per impostare un bit.

number |= 1UL << n;

Questo imposterà il nth di number. ndovrebbe essere zero, se si desidera impostare il 1bit st e così via n-1, se si desidera impostare il nth bit.

Utilizzare 1ULLse numberè più largo di unsigned long; la promozione di 1UL << nnon avviene fino a dopo aver valutato 1UL << ndove il comportamento indefinito si sposta di oltre la larghezza di a long. Lo stesso vale per tutto il resto degli esempi.

Schiarendo un po '

Utilizzare l'operatore AND bit per bit ( &) per cancellare un po '.

number &= ~(1UL << n);

Questo chiarirà il punto ndi number. È necessario invertire la stringa di bit con l'operatore NOT bit a bit ( ~), quindi AND.

Impegnarsi un po '

L'operatore XOR ( ^) può essere usato per alternare un po '.

number ^= 1UL << n;

Ciò commuterà il punto ndi number.

Controllando un po '

Non hai chiesto questo, ma potrei anche aggiungerlo.

Per controllare un po ', sposta il numero n a destra, quindi bit a bit E esso:

bit = (number >> n) & 1U;

Ciò inserirà il valore del nth bit di numbernella variabile bit.

Cambiare il n esimo bit di x

Impostare il nth bit su uno 1o 0può essere ottenuto con quanto segue sull'implementazione C ++ di un complemento di 2:

number ^= (-x ^ number) & (1UL << n);

Il bit nverrà impostato se xè 1, e cancellato se lo xè 0. Se xha qualche altro valore, ottieni spazzatura. x = !!xlo booleanizzerà su 0 o 1.

Per renderlo indipendente dal comportamento di negazione del complemento di 2 (dove -1sono impostati tutti i bit, diversamente dall'implementazione di un complemento di 1 o segno / magnitudine C ++), utilizzare la negazione senza segno.

number ^= (-(unsigned long)x ^ number) & (1UL << n);

o

unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);

In genere è una buona idea utilizzare tipi senza segno per la manipolazione di bit portatile.

o

number = (number & ~(1UL << n)) | (x << n);

(number & ~(1UL << n))cancellerà il nbit th e (x << n)imposterà il nbit th su x.

In genere è anche una buona idea non copiare / incollare il codice in generale e così tante persone usano macro di preprocessore (come la wiki della community più in basso ) o una sorta di incapsulamento.


128
Vorrei notare che su piattaforme che hanno il supporto nativo per bit set / clear (es. Microcontrollori AVR), i compilatori traducono spesso 'myByte | = (1 << x)' nel set di bit nativo / cancella istruzioni ogni volta che x è una costante, es: (1 << 5), o const unsigned x = 5.
Aaron,

52
bit = numero & (1 << x); non inserirà il valore del bit x in bit a meno che bit non abbia il tipo _Bool (<stdbool.h>). Altrimenti, bit = !! (numero & (1 << x)); Will ..
Chris Young,

23
perché non cambi l'ultimo abit = (number >> x) & 1
aaronman il

42
1è un intletterale, che è firmato. Quindi tutte le operazioni qui operano su numeri firmati, che non è ben definito dagli standard. Gli standard non garantiscono il complemento a due o il cambiamento aritmetico, quindi è meglio usarli 1U.
Siyuan Ren,

50
Preferisco number = number & ~(1 << n) | (x << n);cambiare il n-esimo bit in x.
jiasli,

459

Utilizzando la libreria standard C ++: std::bitset<N>.

O il Boost versione: boost::dynamic_bitset.

Non è necessario creare il tuo:

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

[Alpha:] > ./a.out
00010

La versione Boost consente un bitset di dimensioni di runtime rispetto a un set di bit di dimensioni di compilazione della libreria standard .


34
+1. Non che std :: bitset sia utilizzabile da "C", ma poiché l'autore ha taggato la sua domanda con "C ++", AFAIK, la tua risposta è la migliore da queste parti ... std :: vector <bool> è un altro modo, se si conoscono i suoi pro e i suoi contro
paercebal,

23
@andrewdotnich: vector <bool> è (purtroppo) una specializzazione che memorizza i valori come bit. Vedi gotw.ca/publications/mill09.htm per maggiori informazioni ...
Niklas

71
Forse nessuno l'ha menzionato perché è stato etichettato come incorporato. Nella maggior parte dei sistemi embedded si evita STL come la peste. E aumentare il supporto è probabilmente un uccello molto raro da individuare nella maggior parte dei compilatori integrati.
Lundin,

17
@Martin È molto vero. Oltre a specifici Performance Killer come STL e template, molti sistemi embedded evitano addirittura del tutto le librerie standard, perché sono così difficili da verificare. La maggior parte della filiale incorporata sta adottando standard come MISRA, che richiede strumenti di analisi del codice statico (qualsiasi professionista del software dovrebbe utilizzare tali strumenti tra l'altro, non solo persone incorporate). Generalmente le persone hanno cose migliori da fare che eseguire analisi statiche attraverso l'intera libreria standard, se il suo codice sorgente è persino disponibile per loro sul compilatore specifico.
Lundin,

37
@Lundin: le tue dichiarazioni sono eccessivamente ampie (quindi inutili da discutere). Sono sicuro di poter trovare situazioni in cui sono vere. Questo non cambia il mio punto iniziale. Entrambe queste classi sono perfette per l'uso nei sistemi embedded (e so per certo che vengono utilizzate). Anche il tuo punto iniziale su STL / Boost che non viene utilizzato su sistemi embedded è sbagliato. Sono sicuro che ci sono sistemi che non li usano e persino i sistemi che li usano sono usati con giudizio ma dire che non sono usati non è corretto (perché ci sono sistemi dove sono stati usati).
Martin York,

248

L'altra opzione è utilizzare i campi bit:

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

struct bits mybits;

definisce un campo a 3 bit (in realtà, sono tre campi da 1 bit). Le operazioni sui bit ora diventano un po 'più semplici (ahah):

Per impostare o cancellare un po ':

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

Per alternare un po ':

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

Verifica un po ':

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

Funziona solo con campi bit di dimensioni fisse. Altrimenti devi ricorrere alle tecniche di manipolazione dei bit descritte nei post precedenti.


68
Ho sempre trovato che usare bitfield è una cattiva idea. Non hai alcun controllo sull'ordine in cui i bit sono allocati (dall'alto o dal basso), il che rende impossibile serializzare il valore in modo stabile / portatile tranne bit per volta. È anche impossibile mescolare l'aritmetica dei bit fai-da-te con i campi di bit, ad esempio creando una maschera che testa diversi bit contemporaneamente. Ovviamente puoi usare && e sperare che il compilatore lo ottimizzi correttamente ...
R .. GitHub FERMA AIUTANDO ICE il

34
I campi di bit sono cattivi in ​​molti modi, potrei quasi scriverne un libro. In effetti, dovevo quasi farlo per un programma sul campo che richiedeva la conformità MISRA-C. MISRA-C impone che tutti i comportamenti definiti dall'implementazione siano documentati, quindi ho finito per scrivere un saggio su tutto ciò che può andare storto nei campi di bit. Ordine bit, endianess, bit di padding, byte di padding, vari altri problemi di allineamento, conversioni di tipo implicite ed esplicite da e verso un campo di bit, UB se int non viene utilizzato e così via. Usa invece operatori bit per bit per meno bug e codice portatile. I campi di bit sono completamente ridondanti.
Lundin,

44
Come la maggior parte delle funzionalità linguistiche, i campi di bit possono essere utilizzati correttamente o possono essere utilizzati in modo improprio. Se è necessario raggruppare più valori piccoli in un singolo int, i campi di bit possono essere molto utili. D'altra parte, se inizi a fare ipotesi su come i campi di bit si associano all'int di contenimento effettivo, stai solo chiedendo problemi.
Ferruccio,

4
@endolith: Non sarebbe una buona idea. Potresti farlo funzionare, ma non sarebbe necessariamente portabile su un processore diverso, su un compilatore diverso o addirittura sulla prossima versione dello stesso compilatore.
Ferruccio,

3
@Yasky e Ferruccio che ottengono risposte diverse a sizeof () per questo approccio dovrebbero illustrare i problemi di compatibilità non solo tra compilatori ma tra hardware. A volte ci illudiamo di aver risolto questi problemi con le lingue o definito i tempi di esecuzione, ma si riduce davvero a "funzionerà sulla mia macchina?". Ragazzi incorporati, avete il mio rispetto (e le mie simpatie).
Kelly S. francese,

181

Uso le macro definite in un file di intestazione per gestire il set di bit e cancellare:

/* 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

/* x=target variable, y=mask */
#define BITMASK_SET(x,y) ((x) |= (y))
#define BITMASK_CLEAR(x,y) ((x) &= (~(y)))
#define BITMASK_FLIP(x,y) ((x) ^= (y))
#define BITMASK_CHECK_ALL(x,y) (((x) & (y)) == (y))   // warning: evaluates y twice
#define BITMASK_CHECK_ANY(x,y) ((x) & (y))

17
Mi rendo conto che questo è un post di 5 anni, ma non c'è duplicazione di argomenti in nessuna di quelle macro, Dan
Robert Kelly,

11
BITMASK_CHECK(x,y) ((x) & (y))deve essere ((x) & (y)) == (y)altrimenti restituisce un risultato errato sulla maschera multibit (es. 5vs. 3) / * Ciao a tutti i becchini
:)

7
1dovrebbe essere (uintmax_t)1o simile nel caso in cui qualcuno cerchi di usare queste macro su un longtipo o più grande
MM

2
BITMASK_CHECK_ALL(x,y)può essere implementato come!~((~(y))|(x))
Handy999

3
@ Handy999 È un po 'più facile capire perché ciò funzioni dopo l'applicazione della legge di De Morgan e la ri-organizzazione per ottenere!(~(x) & (y))
Tavian Barnes

114

A volte vale la pena usare un enumper nominare i bit:

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

Quindi utilizzare i nomi in seguito. Vale a dire

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

per impostare, cancellare e testare. In questo modo nascondi i numeri magici dal resto del codice.

A parte questo, approvo la soluzione di Jeremy.


1
In alternativa, è possibile eseguire una clearbits()funzione anziché &= ~. Perché stai usando un enum per questo? Ho pensato che fossero per creare un gruppo di variabili uniche con valore arbitrario nascosto, ma stai assegnando un valore definito a ciascuna. Quindi qual è il vantaggio rispetto alla loro definizione come variabili?
endolith,

4
@endolith: l'uso di enums per insiemi di costanti correlate risale a molto tempo fa nella programmazione c. Sospetto che con i compilatori moderni l'unico vantaggio rispetto const shorto comunque sia che siano esplicitamente raggruppati insieme. E quando li desideri per qualcosa di diverso dalle maschere di bit, ottieni la numerazione automatica. In c ++ ovviamente, formano anche tipi distinti che ti danno un po 'di extra controllo statico degli errori.
dmckee --- ex-moderatore gattino

Entrerai in costanti di enum non definite se non definisci una costante per ciascuno dei possibili valori dei bit. Qual è il enum ThingFlagsvalore ThingError|ThingFlag1, ad esempio?
Luis Colorado,

6
Se si utilizza questo metodo, tenere presente che le costanti enum sono sempre di tipo firmato int. Ciò può causare tutti i tipi di bug sottili a causa della promozione implicita di numeri interi o operazioni bit a bit su tipi firmati. thingstate = ThingFlag1 >> 1invocherà ad esempio un comportamento definito dall'implementazione. thingstate = (ThingFlag1 >> x) << ypuò invocare comportamenti indefiniti. E così via. Per sicurezza, esegui sempre il cast di un tipo senza segno.
Lundin,

1
@Lundin: A partire da C ++ 11, è possibile impostare il tipo di base di un elenco, ad esempio: enum My16Bits: unsigned short { ... };
Aiken Drum

47

Da bitops.h di 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)))

OK, analizziamo le cose ...

L'espressione comune con cui sembri avere problemi in tutti questi è "(1L << (posn))". Tutto ciò che fa è creare una maschera con un solo bit acceso e che funzionerà con qualsiasi tipo intero. L'argomento "posn" specifica la posizione in cui si desidera il bit. Se posn == 0, questa espressione valuterà:

0000 0000 0000 0000 0000 0000 0000 0001 binary.

Se posn == 8, valuterà:

0000 0000 0000 0000 0000 0001 0000 0000 binary.

In altre parole, crea semplicemente un campo di 0 con un 1 nella posizione specificata. L'unica parte difficile è nella macro BitClr () in cui è necessario impostare un singolo 0 bit in un campo di 1. Ciò si ottiene utilizzando il complemento 1 della stessa espressione indicata dall'operatore tilde (~).

Una volta creata la maschera, viene applicata all'argomento proprio come suggerisci, usando gli operatori bit per bit e (&), o (|) e xor (^). Poiché la maschera è di tipo long, le macro funzioneranno altrettanto bene su char, short, int o long.

La linea di fondo è che questa è una soluzione generale per un'intera classe di problemi. Naturalmente, è possibile e persino appropriato riscrivere l'equivalente di una di queste macro con valori di maschera espliciti ogni volta che ne hai bisogno, ma perché farlo? Ricorda, la sostituzione della macro si verifica nel preprocessore e quindi il codice generato rifletterà il fatto che i valori sono considerati costanti dal compilatore - vale a dire utilizzare le macro generalizzate è altrettanto efficace che "reinventare la ruota" ogni volta che è necessario manipolare un po '.

Poco convinta? Ecco un po 'di codice di prova: ho usato Watcom C con ottimizzazione completa e senza usare _cdecl, quindi lo smontaggio risultante sarebbe il più pulito possibile:

---- [TEST.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 (smontato)] -------------------------------------- ---------

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] ------------------------------------------- ----------------------


3
2 cose a riguardo: (1) esaminando le macro, alcuni potrebbero erroneamente ritenere che le macro in realtà impostino / cancellino / capovolgano i bit nell'arg, tuttavia non vi è alcuna assegnazione; (2) il tuo test.c non è completo; Sospetto che se avessi corso più casi, avresti trovato un problema (esercizio del lettore)
Dan,

19
-1 Questo è solo uno strano offuscamento. Non reinventare mai il linguaggio C nascondendo la sintassi del linguaggio dietro le macro, è una brutta pratica. Quindi alcune stranezze: prima viene firmato 1L, il che significa che tutte le operazioni sui bit verranno eseguite su un tipo firmato. Tutto ciò che passa a queste macro verrà restituito come firmato a lungo. Non bene. In secondo luogo, funzionerà in modo molto inefficiente su CPU più piccole in quanto si applica a lungo quando le operazioni avrebbero potuto essere a livello int. Terzo, le macro simili a funzioni sono la radice di tutti i mali: non hai nessun tipo di sicurezza. Inoltre, il commento precedente su nessuna assegnazione è molto valido.
Lundin,

2
Questo fallirà se lo argè long long. 1Ldeve essere il tipo più ampio possibile, quindi (uintmax_t)1. (Potresti cavartela 1ull)
MM

Hai ottimizzato per le dimensioni del codice? Sulle CPU tradizionali Intel otterrai stalle a registro parziale durante la lettura di AX o EAX dopo il ritorno di questa funzione, poiché scrive i componenti a 8 bit di EAX. (Va bene su CPU AMD o altri che non rinominano i registri parziali separatamente dal registro completo. Haswell / Skylake non rinominano AL separatamente, ma rinominano AH. ).
Peter Cordes,

37

Usa gli operatori bit a bit: & |

Per impostare l'ultimo bit in 000b:

foo = foo | 001b

Per controllare l'ultimo bit in foo:

if ( foo & 001b ) ....

Per cancellare l'ultimo bit in foo:

foo = foo & 110b

Ho usato XXXbper chiarezza. Probabilmente lavorerai con la rappresentazione HEX, a seconda della struttura dei dati in cui impacchetti i bit.


7
Non esiste alcuna notazione binaria in C. Le costanti di interi binari sono estensioni non standard.
Lundin,

Usa XOR per alternare un po ':foo = foo ^ MY_MASK
Peter L

Utilizzare NOT per invertire una maschera per chiarire:foo = foo & ~MY_MASK
Peter L

32

Per il principiante vorrei spiegare un po 'di più con un esempio:

Esempio:

value is 0x55;
bitnum : 3rd.

L' &operatore viene utilizzato controllare il bit:

0101 0101
&
0000 1000
___________
0000 0000 (mean 0: False). It will work fine if the third bit is 1 (then the answer will be True)

Attiva / Disattiva:

0101 0101
^
0000 1000
___________
0101 1101 (Flip the third bit without affecting other bits)

| operatore: imposta il bit

0101 0101
|
0000 1000
___________
0101 1101 (set the third bit without affecting other bits)

26

Ecco la mia macro aritmetica di bit preferita, che funziona per qualsiasi tipo di array intero senza segno unsigned charfino a size_t(che è il tipo più grande con cui dovrebbe essere efficiente lavorare):

#define BITOP(a,b,op) \
 ((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a)))))

Per impostare un po ':

BITOP(array, bit, |=);

Per cancellare un po ':

BITOP(array, bit, &=~);

Per alternare un po ':

BITOP(array, bit, ^=);

Per testare un po ':

if (BITOP(array, bit, &)) ...

eccetera.


5
È buono da leggere ma si dovrebbe essere consapevoli dei possibili effetti collaterali. L'utilizzo BITOP(array, bit++, |=);in un ciclo molto probabilmente non farà ciò che vuole il chiamante.
foraidt,

Infatti. =) Una variante che potresti preferire è quella di separarla in 2 macro, 1 per indirizzare l'elemento array e l'altra per spostare il bit in posizione, ala BITCELL(a,b) |= BITMASK(a,b);(entrambi prendono acome argomento per determinare la dimensione, ma quest'ultimo non valuterà mai apoiché appare solo in sizeof).
R .. GitHub smette di aiutare ICE il

1
@R .. Questa risposta è molto vecchia, ma probabilmente in questo caso preferirei una funzione a una macro.
PC Luddite,

Minore: il 3 ° (size_t)cast sembra essere lì solo per assicurare una matematica non firmata con %. Potrebbe (unsigned)esserci.
chux - Ripristina Monica il

Il (size_t)(b)/(8*sizeof *(a))inutilmente potrebbe restringere bprima della divisione. Solo un problema con array di bit molto grandi. Ancora una macro interessante.
chux - Ripristina Monica il

25

Dato che questo è etichettato "incorporato", suppongo che stai usando un microcontrollore. Tutti i suggerimenti sopra riportati sono validi e funzionano (leggi-modifica-scrivi, sindacati, strutture, ecc.).

Tuttavia, durante un attacco di debug basato su oscilloscopio, sono rimasto sorpreso di scoprire che questi metodi hanno un notevole sovraccarico nei cicli della CPU rispetto alla scrittura di un valore direttamente nei registri PORTnSET / PORTnCLEAR del micro che fa la differenza in presenza di loop / high stretti -perni di attivazione / disattivazione di ISR.

Per quelli che non hanno familiarità: Nel mio esempio, il micro ha un registro di stato pin generale PORTn che riflette i pin di uscita, quindi facendo PORTn | = BIT_TO_SET si traduce in una lettura-modifica-scrittura in quel registro. Tuttavia, i registri PORTnSET / PORTnCLEAR prendono un '1' per indicare "si prega di rendere questo bit 1" (SET) o "si prega di rendere questo bit zero" (CLEAR) e uno '0' per indicare "lasciare il pin da solo". quindi, finisci con due indirizzi di porta a seconda che tu stia impostando o cancellando il bit (non sempre conveniente) ma una reazione molto più veloce e un codice assemblato più piccolo.


Micro era Coldfire MCF52259, usando C in Codewarrior. Osservare il disassemblatore / asm è un esercizio utile in quanto mostra tutti i passaggi che la CPU deve compiere per eseguire anche le operazioni più basilari. <br> Abbiamo anche individuato altre istruzioni di hogging della CPU in cicli critici in termini di tempo: vincolare una variabile facendo var% = max_val costa un mucchio di cicli CPU ogni volta, mentre se (var> max_val) var- = max_val usa solo un paio di istruzioni. <br> Una buona guida per qualche altro trucco è qui: codeproject.com/Articles/6154/…
John U

Ancora più importante, i registri I / O associati alla memoria dell'helper forniscono un meccanismo per gli aggiornamenti atomici. Leggere / modificare / scrivere può andare molto male se la sequenza viene interrotta.
Ben Voigt,

1
Tenere presente che tutti i registri delle porte saranno definiti come volatilee quindi il compilatore non è in grado di eseguire alcuna ottimizzazione sul codice che coinvolge tali registri. Pertanto, è buona norma smontare tale codice e vedere come è risultato a livello di assemblatore.
Lundin,

24

L'approccio bitfield presenta altri vantaggi nell'arena integrata. È possibile definire una struttura che si associa direttamente ai bit in un particolare registro hardware.

struct HwRegister {
    unsigned int errorFlag:1;  // one-bit flag field
    unsigned int Mode:3;       // three-bit mode field
    unsigned int StatusCode:4;  // four-bit status code
};

struct HwRegister CR3342_AReg;

È necessario essere consapevoli dell'ordine di impacchettamento dei bit: penso che sia prima MSB, ma questo potrebbe dipendere dall'implementazione. Inoltre, verifica come i campi dei gestori del compilatore attraversano i limiti di byte.

È quindi possibile leggere, scrivere, testare i singoli valori come prima.


2
Praticamente tutto sui bit-field è definito dall'implementazione. Anche se riesci a scoprire tutti i dettagli su come il tuo particolare compilatore li implementa, usarli nel tuo codice lo renderà sicuramente non portatile.
Lundin,

1
@Lundin - Vero, ma il sistema di manipolazione dei bit incorporato (in particolare nei registri hardware, che è ciò a cui si riferisce la mia risposta) non sarà mai utilmente trasportabile.
Roddy,

1
Forse non tra CPU completamente diverse. Ma molto probabilmente vuoi che sia portatile tra compilatori e tra diversi progetti. E ci sono molti "bit-fiddling" integrati che non sono affatto legati all'hardware, come la codifica / decodifica del protocollo dati.
Lundin,

... e se hai l'abitudine di usare i campi bit per la programmazione integrata, scoprirai che il tuo codice X86 viene eseguito più velocemente e anche più snello. Non in semplici benchmark in cui hai l'intera macchina per schiacciare il benchmark, ma in ambienti multitasking reali in cui i programmi competono per le risorse. Vantaggio CISC - il cui obiettivo progettuale originale era quello di compensare le CPU più velocemente dei bus e della memoria lenta.

20

Controlla un po 'in una posizione arbitraria in una variabile di tipo arbitrario:

#define bit_test(x, y)  ( ( ((const char*)&(x))[(y)>>3] & 0x80 >> ((y)&0x07)) >> (7-((y)&0x07) ) )

Esempio di utilizzo:

int main(void)
{
    unsigned char arr[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };

    for (int ix = 0; ix < 64; ++ix)
        printf("bit %d is %d\n", ix, bit_test(arr, ix));

    return 0;
}

Note: Questo è progettato per essere veloce (data la sua flessibilità) e non ramoso. Il risultato è un codice macchina SPARC efficiente durante la compilazione di Sun Studio 8; L'ho anche testato usando MSVC ++ 2008 su amd64. È possibile creare macro simili per impostare e cancellare i bit. La differenza chiave di questa soluzione rispetto a molte altre qui è che funziona per qualsiasi posizione praticamente in qualsiasi tipo di variabile.


20

Più generale, per bitmap di dimensioni arbitrarie:

#define BITS 8
#define BIT_SET(  p, n) (p[(n)/BITS] |=  (0x80>>((n)%BITS)))
#define BIT_CLEAR(p, n) (p[(n)/BITS] &= ~(0x80>>((n)%BITS)))
#define BIT_ISSET(p, n) (p[(n)/BITS] &   (0x80>>((n)%BITS)))

2
CHAR_BITè già definito da limits.h, non è necessario inserire il proprio BITS(e in effetti peggiori il tuo codice nel farlo)
MM

14

Questo programma consente di modificare qualsiasi bit di dati da 0 a 1 o da 1 a 0:

{
    unsigned int data = 0x000000F0;
    int bitpos = 4;
    int bitvalue = 1;
    unsigned int bit = data;
    bit = (bit>>bitpos)&0x00000001;
    int invbitvalue = 0x00000001&(~bitvalue);
    printf("%x\n",bit);

    if (bitvalue == 0)
    {
        if (bit == 0)
            printf("%x\n", data);
        else
        {
             data = (data^(invbitvalue<<bitpos));
             printf("%x\n", data);
        }
    }
    else
    {
        if (bit == 1)
            printf("elseif %x\n", data);
        else
        {
            data = (data|(bitvalue<<bitpos));
            printf("else %x\n", data);
        }
    }
}

14

Se stai facendo un po 'di manipolazione, potresti voler usare maschere che renderanno il tutto più veloce. Le seguenti funzioni sono molto veloci e sono ancora flessibili (consentono di modificare i bit in bitmap di qualsiasi dimensione).

const unsigned char TQuickByteMask[8] =
{
   0x01, 0x02, 0x04, 0x08,
   0x10, 0x20, 0x40, 0x80,
};


/** Set bit in any sized bit mask.
 *
 * @return    none
 *
 * @param     bit    - Bit number.
 * @param     bitmap - Pointer to bitmap.
 */
void TSetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] |= TQuickByteMask[n];        // Set bit.
}


/** Reset bit in any sized mask.
 *
 * @return  None
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TResetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] &= (~TQuickByteMask[n]);    // Reset bit.
}


/** Toggle bit in any sized bit mask.
 *
 * @return   none
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TToggleBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] ^= TQuickByteMask[n];        // Toggle bit.
}


/** Checks specified bit.
 *
 * @return  1 if bit set else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitSet( short bit, const unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;    // Index to byte.
    n = bit % 8;    // Specific bit in byte.

    // Test bit (logigal AND).
    if (bitmap[x] & TQuickByteMask[n])
        return 1;

    return 0;
}


/** Checks specified bit.
 *
 * @return  1 if bit reset else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitReset( short bit, const unsigned char *bitmap)
{
    return TIsBitSet(bit, bitmap) ^ 1;
}


/** Count number of bits set in a bitmap.
 *
 * @return   Number of bits set.
 *
 * @param    bitmap - Pointer to bitmap.
 * @param    size   - Bitmap size (in bits).
 *
 * @note    Not very efficient in terms of execution speed. If you are doing
 *        some computationally intense stuff you may need a more complex
 *        implementation which would be faster (especially for big bitmaps).
 *        See (http://graphics.stanford.edu/~seander/bithacks.html).
 */
int TCountBits( const unsigned char *bitmap, int size)
{
    int i, count = 0;

    for (i=0; i<size; i++)
        if (TIsBitSet(i, bitmap))
            count++;

    return count;
}

Nota, per impostare il bit 'n' in un numero intero a 16 bit, procedi come segue:

TSetBit( n, &my_int);

Sta a te assicurarti che il numero di bit sia compreso nell'intervallo della mappa di bit che passi. Nota che per i processori little endian che byte, parole, dwords, qwords, ecc., Si mappano correttamente l'uno con l'altro in memoria (motivo principale per cui i processori little endian sono "migliori" dei processori big endian, ah, sento che sta arrivando una guerra di fiamma su...).


2
Non utilizzare una tabella per una funzione che può essere implementata con un singolo operatore. TQuickByteMask [n] è equivalente a (1 << n). Inoltre, rendere brevi i tuoi argomenti è una pessima idea. Il / e% sarà effettivamente una divisione, non bit-shift / bit-bit e, poiché la divisione firmata per una potenza di 2 non può essere implementata bit a bit. Dovresti rendere il tipo di argomento unsigned int!
R .. GitHub smette di aiutare ICE il

Che senso ha questo? Rende solo il codice più lento e più difficile da leggere? Non riesco a vedere un singolo vantaggio con esso. 1u << n è più facile da leggere per i programmatori C, e si spera che possa essere tradotto in una singola istruzione tick tick CPU. La divisione, d'altra parte, verrà tradotta in qualcosa di circa 10 tick, o addirittura cattiva fino a 100 tick, a seconda di quanto scarsamente l'architettura specifica gestisca la divisione. Per quanto riguarda la funzione bitmap, sarebbe più sensato avere una tabella di ricerca che traduca ogni indice di bit in un indice di byte, per ottimizzare la velocità.
Lundin,

2
Per quanto riguarda big / little endian, big endian mapperà numeri interi e dati grezzi (ad esempio stringhe) allo stesso modo: da sinistra a destra da msb a lsb nell'intera bitmap. Mentre little endian mapperà numeri interi da sinistra a destra come 7-0, 15-8, 23-18, 31-24, ma i dati grezzi sono ancora da sinistra a destra da msb a lsb. Quindi quanto il piccolo endian sia migliore per il tuo particolare algoritmo è completamente al di là di me, sembra essere il contrario.
Lundin,

2
@R .. Una tabella può essere utile se la tua piattaforma non può spostarsi in modo efficiente, come i vecchi microchip MCU, ma ovviamente la divisione nel campione è assolutamente inefficiente
jeb

12

Usa questo:

int ToggleNthBit ( unsigned char n, int num )
{
    if(num & (1 << n))
        num &= ~(1 << n);
    else
        num |= (1 << n);

    return num;
}

5
Bene, usa ramificazioni inefficienti.
asdf,

3
@asdf Il compito del compilatore è di produrre il file binario più efficiente, il compito del programmatore è di scrivere un codice chiaro
MM

3
Questa è una buona dimostrazione di test, impostazione e cancellazione di un particolare bit. Tuttavia è un pessimo approccio per alternare un po '.
Ben Voigt,

10

Espandendo sulla bitsetrisposta:

#include <iostream>
#include <bitset>
#include <string>

using namespace std;
int main() {
  bitset<8> byte(std::string("10010011");

  // Set Bit
  byte.set(3); // 10010111

  // Clear Bit
  byte.reset(2); // 10010101

  // Toggle Bit
  byte.flip(7); // 00010101

  cout << byte << endl;

  return 0;
}

10

Se si desidera eseguire tutte queste operazioni con la programmazione C nel kernel Linux, suggerisco di utilizzare le API standard del kernel Linux.

Vedi https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html

set_bit  Atomically set a bit in memory
clear_bit  Clears a bit in memory
change_bit  Toggle a bit in memory
test_and_set_bit  Set a bit and return its old value
test_and_clear_bit  Clear a bit and return its old value
test_and_change_bit  Change a bit and return its old value
test_bit  Determine whether a bit is set

Nota: qui l'intera operazione avviene in un unico passaggio. Quindi, tutti questi sono garantiti per essere atomici anche sui computer SMP e sono utili per mantenere la coerenza tra i processori.


9

Visual C 2010, e forse molti altri compilatori, hanno il supporto diretto per le operazioni booleane integrate. Un po 'ha due possibili valori, proprio come un booleano, quindi possiamo usare i booleani, anche se occupano più spazio di un singolo bit in memoria in questa rappresentazione. Questo funziona, anche l' sizeof()operatore funziona correttamente.

bool    IsGph[256], IsNotGph[256];

//  Initialize boolean array to detect printable characters
for(i=0; i<sizeof(IsGph); i++)  {
    IsGph[i] = isgraph((unsigned char)i);
}

Quindi, alla tua domanda, IsGph[i] =1o IsGph[i] =0semplifica l'impostazione e l'eliminazione dei bool.

Per trovare caratteri non stampabili:

//  Initialize boolean array to detect UN-printable characters, 
//  then call function to toggle required bits true, while initializing a 2nd
//  boolean array as the complement of the 1st.
for(i=0; i<sizeof(IsGph); i++)  {
    if(IsGph[i])    {
         IsNotGph[i] = 0;
    }   else   {
         IsNotGph[i] = 1;
    }
}

Nota che non c'è nulla di "speciale" in questo codice. Tratta un po 'come un intero - che tecnicamente lo è. Un numero intero a 1 bit che può contenere solo 2 valori e 2 valori.

Una volta ho usato questo approccio per trovare record di prestito duplicati, dove loan_number era la chiave ISAM, usando il numero di prestito a 6 cifre come indice nell'array di bit. Rapidamente veloce, e dopo 8 mesi, ha dimostrato che il sistema mainframe da cui stavamo ottenendo i dati era in effetti mal funzionante. La semplicità delle matrici di bit rende la fiducia nella loro correttezza molto elevata, ad esempio rispetto a un approccio di ricerca.


std :: bitset è effettivamente implementato come bit dalla maggior parte dei compilatori
galinette,

@galinette, d'accordo. Il file di intestazione #include <bitset> è una buona risorsa in questo senso. Inoltre, il vettore di classe speciale <bool> per quando è necessario modificare le dimensioni del vettore. Il C ++ STL, 2a edizione, Nicolai M. Josuttis li copre esaustivamente rispettivamente sulle pagine 650 e 281. C ++ 11 aggiunge alcune nuove funzionalità a std :: bitset, di particolare interesse per me è una funzione hash in contenitori non ordinati. Grazie per il testa a testa! Ho intenzione di eliminare il mio commento sui crampi cerebrali. Già abbastanza spazzatura sul web. Non voglio aggiungere ad esso.

3
Questo utilizza almeno un intero byte di memoria per ciascuno bool. Forse anche 4 byte per le configurazioni C89 che usano intper implementarebool
MM

@MattMcNabb, hai ragione. In C ++ la dimensione del tipo int necessaria per implementare un valore booleano non è specificata dallo standard. Mi sono reso conto che questa risposta era in errore qualche tempo fa, ma ho deciso di lasciarla qui perché la gente sembra trovarla utile. Per coloro che desiderano utilizzare punte commento di galinette è più utile come lo è la mia libreria po 'qui ... stackoverflow.com/a/16534995/1899861

2
@RocketRoy: probabilmente vale la pena cambiare la frase che afferma che questo è un esempio di "operazioni bit".
Ben Voigt,

6

Utilizzare uno degli operatori come definito qui .

Per impostare un bit, utilizzato int x = x | 0x?;dove si ?trova la posizione del bit in forma binaria.


2
0xè il prefisso per un letterale in esadecimale, non binario.
Ben Voigt

5

Ecco alcune macro che uso:

SET_FLAG(Status, Flag)            ((Status) |= (Flag))
CLEAR_FLAG(Status, Flag)          ((Status) &= ~(Flag))
INVALID_FLAGS(ulFlags, ulAllowed) ((ulFlags) & ~(ulAllowed))
TEST_FLAGS(t,ulMask, ulBit)       (((t)&(ulMask)) == (ulBit))
IS_FLAG_SET(t,ulMask)             TEST_FLAGS(t,ulMask,ulMask)
IS_FLAG_CLEAR(t,ulMask)           TEST_FLAGS(t,ulMask,0)

5

Variabile utilizzata

int value, pos;

valore - Dati
pos - posizione del bit che ci interessa impostare, cancellare o attivare.

Imposta un po ':

value = value | 1 << pos;

Cancella un po ':

value = value & ~(1 << pos); 

Attiva un po ':

value = value ^ 1 << pos;

5
int set_nth_bit(int num, int n){    
    return (num | 1 << n);
}

int clear_nth_bit(int num, int n){    
    return (num & ~( 1 << n));
}

int toggle_nth_bit(int num, int n){    
    return num ^ (1 << n);
}

int check_nth_bit(int num, int n){    
    return num & (1 << n);
}

Il tipo di ritorno check_nth_bitpuò essere bool.
Xeverous

@Xeverous sì, dipende dall'intenzione dei chiamanti
Sazzad Hissain Khan,

5

Supponiamo che prima
num = 55 intere poche operazioni eseguano operazioni bit per bit (imposta, ottieni, cancella, attiva / disattiva).
n = 4Posizione bit basata su 0 per eseguire operazioni bit a bit.

Come ottenerlo?

  1. Per ottenere il nthbit di num shift giusto num, nvolte. Quindi eseguire bit AND AND &con 1.
bit = (num >> n) & 1;

Come funziona?

       0011 0111 (55 in decimal)
    >>         4 (right shift 4 times)
-----------------
       0000 0011
     & 0000 0001 (1 in decimal)
-----------------
    => 0000 0001 (final result)

Come impostare un po '?

  1. Per impostare un determinato numero di numeri. Spostamento a sinistra 1 nvolte. Quindi eseguire l' |operazione OR bit a bit con num.
num |= (1 << n);    // Equivalent to; num = (1 << n) | num;

Come funziona?

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
       0001 0000
     | 0011 0111 (55 in decimal)
-----------------
    => 0001 0000 (final result)

Come cancellare un po '?

  1. Spostamento a sinistra 1, nvolte ie 1 << n.
  2. Eseguire il complemento bit per bit con il risultato sopra. In modo che l'ennesimo bit non sia impostato e il resto del bit sia impostato, ad es~ (1 << n) .
  3. Infine, eseguire l' &operazione AND bit per bit con il risultato sopra e num. I tre passaggi precedenti possono essere scritti come num & (~ (1 << n));

I passaggi per cancellare un po '

num &= (~(1 << n));    // Equivalent to; num = num & (~(1 << n));

Come funziona?

       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
     ~ 0001 0000
-----------------
       1110 1111
     & 0011 0111 (55 in decimal)
-----------------
    => 0010 0111 (final result)

Come alternare un po '?

Per alternare un po 'usiamo XOR bit a bit ^ operatore . L'operatore XOR bit a bit restituisce 1 se il bit corrispondente di entrambi gli operandi è diverso, altrimenti a 0.

Ciò significa che è necessario attivare o disattivare un po ', è necessario eseguire l'operazione XOR con il bit che si desidera attivare e 1.

num ^= (1 << n);    // Equivalent to; num = num ^ (1 << n);

Come funziona?

  • Se il bit da attivare è 0, 0 ^ 1 => 1 .
  • Se il bit da attivare è 1, allora 1 ^ 1 => 0.
       0000 0001 (1 in decimal)
    <<         4 (left shift 4 times)
-----------------
       0001 0000
     ^ 0011 0111 (55 in decimal)
-----------------
    => 0010 0111 (final result)

Lettura consigliata - Esercitazioni bit per operatore


Grazie per la spiegazione dettagliata. Ecco il link per il problema di pratica per BIT Magia collegamento
Chandra Shekhar

4

Come si imposta, cancella e si attiva / disattiva un singolo bit?

Affrontare un errore comune nella codifica quando si tenta di formare la maschera:
1non è sempre abbastanza ampio

Quali problemi si verificano quando numberè un tipo più ampio di 1?
xpotrebbe essere troppo grande per il turno che 1 << xporta a comportamenti indefiniti (UB). Anche se xnon è troppo grande, ~potrebbe non capovolgere abbastanza bit più significativi.

// assume 32 bit int/unsigned
unsigned long long number = foo();

unsigned x = 40; 
number |= (1 << x);  // UB
number ^= (1 << x);  // UB
number &= ~(1 << x); // UB

x = 10;
number &= ~(1 << x); // Wrong mask, not wide enough

Per assicurarsi che 1 sia abbastanza largo:

Il codice potrebbe usare 1ullo pedanticamente (uintmax_t)1e consentire al compilatore di ottimizzare.

number |= (1ull << x);
number |= ((uintmax_t)1 << x);

O cast - che rende problemi di codifica / revisione / manutenzione mantenendo il cast corretto e aggiornato.

number |= (type_of_number)1 << x;

O promuovi delicatamente 1forzando un'operazione matematica che è larga almeno quanto il tipo di number.

number |= (number*0 + 1) << x;

Come la maggior parte manipolazioni bit, meglio lavorare con unsigned tipi piuttosto che sottoscritti quelli


Aspetto interessante su una vecchia domanda! Né number |= (type_of_number)1 << x;number |= (number*0 + 1) << x;opportuno impostare il bit di segno di un tipo firmato ... È un dato di fatto, nessuno dei due è number |= (1ull << x);. Esiste un modo portatile per farlo in base alla posizione?
Chqrlie,

1
@chqrlie IMO, il modo migliore per evitare di impostare il bit di segno e rischiare UB o IDB con turni è utilizzare tipi senza segno . Altamente spostamento portatile firmato il codice è troppo contorto per essere accettabile.
chux - Ripristina Monica il

3

Una versione modello C ++ 11 (inserita in un'intestazione):

namespace bit {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bit) {variable |=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bit) {variable &= ~((T1)1 << bit);}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bit) {variable ^=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline bool test (T1 &variable, T2 bit) {return variable & ((T1)1 << bit);}
}

namespace bitmask {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bits) {variable |= bits;}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bits) {variable &= ~bits;}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bits) {variable ^= bits;}
    template <typename T1, typename T2> inline bool test_all(T1 &variable, T2 bits) {return ((variable & bits) == bits);}
    template <typename T1, typename T2> inline bool test_any(T1 &variable, T2 bits) {return variable & bits;}
}

Questo codice è rotto. (Inoltre, perché hai ;dopo le definizioni delle tue funzioni?)
melpomene,

@melpomene Il codice non è rotto, l'ho provato. Vuoi dire che non verrà compilato o che il risultato è sbagliato? Informazioni sul extra ";" Non ricordo, quelli possono essere rimossi davvero.
Joakim L. Christiansen,

(variable & bits == bits)?
melpomene,

Grazie per averlo notato, avrebbe dovuto essere((variable & bits) == bits)
Joakim L. Christiansen il

utilizzare std::bitsetin c ++ 11
pqnet il

0

Questo programma è basato sulla soluzione precedente di @ Jeremy. Se qualcuno desidera giocare rapidamente.

public class BitwiseOperations {

    public static void main(String args[]) {

        setABit(0, 4); // set the 4th bit, 0000 -> 1000 [8]
        clearABit(16, 5); // clear the 5th bit, 10000 -> 00000 [0]
        toggleABit(8, 4); // toggle the 4th bit, 1000 -> 0000 [0]
        checkABit(8,4); // check the 4th bit 1000 -> true 
    }

    public static void setABit(int input, int n) {
        input = input | ( 1 << n-1);
        System.out.println(input);
    }


    public static void clearABit(int input, int n) {
        input = input & ~(1 << n-1);
        System.out.println(input);
    }

    public static void toggleABit(int input, int n) {
        input = input ^ (1 << n-1);
        System.out.println(input);
    }

    public static void checkABit(int input, int n) {
        boolean isSet = ((input >> n-1) & 1) == 1; 
        System.out.println(isSet);
    }
}


Output :
8
0
0
true

-2

Prova una di queste funzioni nel linguaggio C per cambiare n bit:

char bitfield;

// Start at 0th position

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & (~( (1 << n) ^ (value << n) ));
}

O

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & ((value << n) | ((~0) ^ (1 << n)));
}

O

void chang_n_bit(int n, int value)
{
    if(value)
        bitfield |= 1 << n;
    else
        bitfield &= ~0 ^ (1 << n);
}

char get_n_bit(int n)
{
    return (bitfield & (1 << n)) ? 1 : 0;
}

value << npuò causare un comportamento indefinito
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.