Operatori Bitwise in Linguaggio C
Gli operatori bitwise, chiamati anche operatori bit a bit, in C consentono di manipolare i singoli bit dei numeri interi, offrendo un controllo di basso livello spesso essenziale per ottimizzare operazioni come la gestione di flag, maschere e spostamenti bit a scopo di calcolo.
Con sei operatori disponibili (due per gli shift, quattro per le operazioni booleane), il linguaggio offre una notevole flessibilità nel trattamento delle informazioni a livello binario.
Operatori Bitwise
il linguaggio C mette a disposizione sei operatori bitwise, che operano su dati interi a livello di singolo bit.
Discuteremo per primi i due operatori di shift o scorrimento bitwise, seguiti dagli altri quattro operatori bitwise (complemento bitwise, AND bitwise, XOR bitwise e OR bitwise).
Operatori di Shift Bitwise
Gli operatori di shift bitwise possono trasformare la rappresentazione binaria di un intero, spostando i suoi bit a sinistra o a destra. C fornisce due operatori di shift, mostrati nella Tabella che segue:
Simbolo | Significato |
---|---|
<< |
shift a sinistra |
>> |
shift a destra |
Gli operandi per <<
e >>
possono essere di qualunque tipo intero (incluso char
). Entrambi gli operandi vengono promossi a interi e il risultato ha il tipo dell'operando sinistro dopo la promozione.
-
Il valore di
i << j
è il risultato di spostare i bit ini
verso sinistra dij
posizioni. Per ogni bit che scivola fuori dall'estremità sinistra dii
, un bit 0 entra da destra.Figura 1: Operazione di Shift a Sinistra -
Il valore di
i >> j
è il risultato di spostare i bit ini
verso destra dij
posizioni. Se si tratta di un tipo unsigned o se il valore dii
è non negativo, vengono aggiunti 0 da sinistra secondo necessità. Sei
è un numero negativo, il risultato dipende dall'implementazione: in alcuni compilatori verranno aggiunti 0, in altri verrà preservato il bit di segno.Figura 2: Operazione di Shift a Destra
Preferire gli unsigned
per gli Operatori di Shift
È preferibile eseguire operazioni di shift solo su numeri unsigned
, in modo da evitare comportamenti dipendenti dall'implementazione.
Nel seguente esempio, si mostra l'effetto dell'applicare gli operatori di shift al numero 13 (rappresentato in binario). Per semplicità, si assume di usare short
a 16 bit:
unsigned short i, j;
i = 13; /* i vale ora 13 (binario 0000000000001101) */
j = i << 2; /* j vale ora 52 (binario 0000000000110100) */
i >> 2; /* i vale ora 3 (binario 0000000000000011) */
Come mostrano questi esempi, nessun operatore modifica i propri operandi. Per modificare il valore di una variabile spostando i suoi bit, useremo gli operatori di assegnamento combinati <<=
e >>=
:
i = 13; /* i vale ora 13 (binario 0000000000001101) */
i <<= 2; /* i vale ora 52 (binario 0000000000110100) */
i >>= 2; /* i vale ora 13 (binario 0000000000001101) */
Precedenza degli Operatori di Shift
Gli operatori di shift bitwise hanno precedenza più bassa rispetto agli operatori aritmetici, il che può causare sorprese. Ad esempio, i << 2 + 1
significa (i << 2) + 1
, non (i << (2 + 1))
.
Complemento Bitwise, AND, Exclusive OR e Inclusive OR
La Tabella seguente elenca gli altri operatori bitwise.
Simbolo | Significato |
---|---|
~ |
complemento bitwise |
& |
AND bitwise |
^ |
XOR (or esclusivo) |
| |
OR (or inclusivo) |
- L'operatore
~
è unario; su di esso vengono effettuate le promozioni a intero. - Gli altri operatori (
&
,^
,|
) sono binari e su di essi si applicano le usuali conversioni aritmetiche.
Gli operatori ~
, &
, ^
e |
eseguono operazioni booleane su tutti i bit dei rispettivi operandi:
~
(complemento) inverte i bit: gli 0 diventano 1 e gli 1 diventano 0.&
esegue l'operazione booleana AND su ogni coppia di bit corrispondenti.^
e|
sono simili, eseguono un'operazione booleana OR sui bit corrispondenti. Tuttavia:^
(XOR) produce 0 quando entrambi i bit hanno valore 1, altrimenti produce 1.|
(OR) produce 1 se almeno uno dei bit corrispondenti vale 1.
Gli operatori Bitwise non sono Operatori Logici
Non confondere gli operatori bitwise &
e |
con gli operatori logici &&
e ||
.
Gli operatori bitwise talvolta producono lo stesso risultato degli operatori logici, ma non sono equivalenti.
Nel seguente esempio si illustra l'effetto di ~
, &
, ^
e |
:
unsigned short i, j, k;
i = 21; /* i vale ora 21 (binario 0000000000010101) */
j = 56; /* j vale ora 56 (binario 0000000000111000) */
k = ~i; /* k vale ora 65514 (binario 1111111111101010) */
k = i & j; /* k vale ora 16 (binario 0000000000010000) */
k = i ^ j; /* k vale ora 45 (binario 0000000000101101) */
(Il valore mostrato per ~i
si basa sull'assunzione che un unsigned short
occupi 16 bit.)
L'operatore ~
merita un'attenzione speciale, poiché ci consente di gestire i bit a basso livello in modo più portabile. Se abbiamo bisogno di un intero in cui tutti i bit siano pari a 1, possiamo scrivere ~0
, che non dipende dal numero di bit in un intero. Allo stesso modo, se serve un intero i cui bit siano tutti 1 tranne l'ultimo, potremmo scrivere ~0x1f
.
Precedenza degli Operatori Bitwise
Ognuno degli operatori ~
, &
, ^
e |
ha precedenza diversa. Dall'alto al basso:
~
&
^
|
Di conseguenza, possiamo combinare questi operatori in espressioni senza dover usare parentesi. Ad esempio, potremmo scrivere i & ~j | k
invece di (i & (~j)) | k
e i ^ j & ~k
invece di i ^ (j & (~k))
. Ovviamente, nulla ci impedisce di usare le parentesi per evitare confusione.
Precedenza degli Operatori Bitwise e Relazionali
La precedenza di &
, ^
e |
è più bassa rispetto a quella degli operatori relazionali ed uguaglianza. Di conseguenza, istruzioni come la seguente non hanno l'effetto desiderato:
if (status & 0x4000 != 0)
Invece di verificare se status & 0x4000
è diverso da zero, l'istruzione valuta 0x4000 != 0
(che ha il valore 1) e poi verifica se status & 1
non è zero.
Operatori di Assegnamento Combinati per Bitwise
Gli operatori di assegnamento combinati &=
, ^=
, |=
corrispondono rispettivamente agli operatori bitwise &
, ^
e |
:
i = 21; /* i è ora 21 (binario 0000000000010101) */
i = 56; /* i è ora 56 (binario 0000000000111000) */
i &= 5; /* i è ora 56 & 5 = 0b111000 & 0b00101 = 40 (decimale) */
i ^= j; /* i è ora 40 ^ j */
i |= j; /* i è ora ... ecc. */
Come visto, queste forme di assegnamento consentono di modificare direttamente il contenuto di una variabile applicandole un'operazione bitwise.
In Sintesi
I concetti chiave studiati in questa lezione sono:
- Operatori di Shift (
<<
e>>
): spostano i bit di un valore a sinistra o a destra, riempiendo gli spazi con zeri o preservando il segno. - Operatori bitwise (
~
,&
,^
,|
): permettono complementi, AND, XOR e OR tra i singoli bit di due operandi. - Precedenza: gli operatori di shift e bitwise hanno precedenze diverse da quelli aritmetici, relazionali e logici. È importante usare le parentesi se si vuole un ordine di valutazione differente.
- Sicurezza e portabilità: per evitare comportamenti non definiti o dipendenti dall'implementazione, conviene utilizzare gli shift su tipi unsigned e fare attenzione alle differenze tra operatori bitwise (
&
,|
) e logici (&&
,||
). - Assegnamenti combinati:
<<=
,>>=
,&=
,^=
,|=
consentono di eseguire sia l'operazione bitwise sia l'assegnamento, rendendo il codice più compatto.