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
Tabella 1: Operatori di scorrimento bit a bit in C.

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 in i verso sinistra di j posizioni. Per ogni bit che scivola fuori dall'estremità sinistra di i, un bit 0 entra da destra.

    Operazione di Shift a Sinistra
    Figura 1: Operazione di Shift a Sinistra
  • Il valore di i >> j è il risultato di spostare i bit in i verso destra di j posizioni. Se si tratta di un tipo unsigned o se il valore di i è non negativo, vengono aggiunti 0 da sinistra secondo necessità. Se i è un numero negativo, il risultato dipende dall'implementazione: in alcuni compilatori verranno aggiunti 0, in altri verrà preservato il bit di segno.

    Operazione di Shift a Destra
    Figura 2: Operazione di Shift a Destra
Consiglio

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

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)
Tabella 2: Altri operatori bitwise in C.
  • 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.
Nota

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:

  1. ~
  2. &
  3. ^
  4. |

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.

Nota

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.