In questo articolo vi mostrerò alcune tecniche e suggerimenti che vi consentiranno di acquisire una certa dimestichezza con l’aspetto più a basso livello del linguaggio C: le operazioni binarie o bitwise. Si presuppone una conoscenza di base del linguaggio, dimestichezza con il proprio sistema operativo e il compilatore installato. Il tutorial è stato scritto e testato su GNU/Linux con compilatore GCC, ma i concetti sono universalmente applicabili a qualsiasi sistema operativo e compilatore C da trent’anni a questa parte.

Il C fornisce alcuni operatori che, opportunamente combinati all’interno di espressioni, consentono la manipolazione dei singoli bit all’interno di un dato numerico. Per seguire proficuamente questo articolo è consigliabile, ma non indispensabile, affiancarsi ad un manuale di programmazione in C. Il mio consiglio, nel caso non ne aveste ancora comprato uno, è di affidarsi al buon vecchio Kernighan & Ritchie.

Gli operatori binari che useremo in questo tutorial sono:

a & b

– Bitwise AND

a >> b 

– Right Shift

e inoltre useremo l’operatore ternario a ? b : c che è un operatore di selezione, simile al costrutto if..then..else.

Per studiare il funzionamento di questi operatori vi mostro un programmino completo di poche righe di codice che andremo ad analizzare in seguito:

#include 

int main(int argc, char **argv)
{
	int c, i;

	for (;;)
	{
		c = getchar();

		if (c == EOF)
			break;

		if (c == '\n')
		{
			printf("\n");
			continue;
		}

		for (i=7; i>=0; i--)
		{
			printf(((c >> i) & 1) ? "1" : "0");
		}
		printf(" ");
	}

	return 0;
}

La struttura generale del codice dovrebbe essere familiare a chi ha almeno una conoscenza basilare del linguaggio. Se non avete chiaro il funzionamento del codice, vi consiglio di seguire prima qualche tutorial introduttivo e di ritornare in seguito.

La porzione di codice che andremo a studiare è la seguente:

for (i=7; i>=0; i--)
{
	printf(((c >> i) & 1) ? "1" : "0");
}
printf(" ");

Sostanzialmente eseguiamo un ciclo 8 volte, stampando qualcosa, che ancora non conosciamo, tramite la funzione printf, e alla fine stampiamo uno spazio vuoto. Esaminiamo nel dettaglio l’espressione:

((c >> i) & 1) ? "1" : "0"

Procediamo come farebbe il compilatore, partendo cioè dalla parentesi più annidata e risolvendo via via i livelli più esterni, fino a risolvere l’intera espressione.

((c >> i) & 1) ? "1" : "0"

in questa espressione evidenziata, usiamo l’operatore di shift binario a destra. Il suo funzionamento è facilmente spiegato con un paio di esempi:

Es: 14 >> 1

rappresentiamo il numero 14 in binario (che è la rappresentazione numerica “naturale” di qualunque CPU), con l’aiuto di carta e penna, o di una calcolatrice, troviamo la sua rappresentazione binaria:

1110

Aggiungiamo degli zeri a sinistra in modo da ottenere un numero di 32 cifre (32 bit), questa è la dimensione di un int in una macchina a 32 bit. Otteniamo quindi:

0000000000000000 0000000000001110

adesso eseguiamo uno shift a destra di 1 unità:

0000000000000000 0000000000000111

Come vedete, tutti i bit sono stati spostati di 1 posto verso destra, il bit più a destra si è praticamente “perso” e il bit più a sinistra ha assunto il valore 0.

Analogamente avremo:

459 >> 3
0000000000000000 0000000111001011
0000000000000000 0000000000111001
12054 >> 5
0000000000000000 0010111100010110
0000000000000000 0000000101111000

Adesso che abbiamo visto come funziona lo shift a destra, vediamo cosa succede con il secondo operatore:

((c >> i) & 1) ? "1" : "0"

Questo è il bitwise AND &, da non confondersi con l’AND logico che in C è rappresentato dall’operatore &&. Questo operatore confronta i due operandi bit per bit, e restituisce un bit pari a 1 solo se entrambi gli operandi hanno il bit pari a 1 in quella data posizione.

Il funzionamento in pratica è molto semplice, e ve lo mostro con alcuni esempi numerici:

89 & 1
00000000000000000000000001011001 &
00000000000000000000000000000001 =
----------------------------------
00000000000000000000000000000001
240883 & 367042
00000000000000111010110011110011 &
00000000000001011001100111000010 =
----------------------------------
00000000000000011000100011000010

Combinando insieme questi due operatori, otteniamo un “effetto combinato” molto potente. Cioè siamo in grado di ricavare il valore di ogni singolo bit di una data variabile. Come ciò avvenga è presto spiegato.

Supponiamo di voler conoscere il valore del bit in posizione 5 della variabile A, basta usare questa espressione:

(A >> 5) & 1>

Ammettiamo che in un dato momento sia A = 12054, vediamo cosa accade:

(12054 >> 5) & 1

0000000000000000 0010111100010110 >> 5
--------------------------------------
0000000000000000 0000000101111000 &
0000000000000000 0000000000000001 =
-----------------------------------
000000000000000000000000000000000

Abbiamo ottenuto il valore 0. Vediamo cosa accade se vogliamo conoscere il valore del bit in posizione 4:

(12054 >> 4) & 1

0000000000000000 0010111100010110 >> 4
--------------------------------------
0000000000000000 0000001011110001 &
0000000000000000 0000000000000001 =
-----------------------------------
000000000000000000000000000000001

Abbiamo ottenuto il valore 1, che è proprio il valore del bit in posizione 4!

Usando questa tecnica all’interno di un ciclo for possiamo conoscere, uno alla volta, il valore di tutti i bit di una certa variabile. Come stamparli a schermo? Analizziamo nuovamente il frammento di codice:

for (i=7; i>=0; i--)
{
	printf(((c >> i) & 1) ? "1" : "0");
}
printf(" ");

Il trucco sta nello sfruttare l’operatore ternario a ? b : c. Come accennato, funziona in modo analogo al costrutto if..then..else ed effettivamente si può tradurre in italiano come: se “a” è diverso da zero allora “b”, altrimenti “c”.

Applicandolo alla nostra espressione, questo significa a sua volta che: se l’espressione ((c >> i) & 1) sarà uguale a 1, allora restituisci la stringa “1”, altrimenti restituisci la stringa “0”. Ma abbiamo visto prima che l’espressione ((c >> i) & 1) serve per ricavare il valore del bit in posizione i all’interno della variabile c, quindi tutto il ciclo non fa altro che scansionare tutti i bit da 7 fino a 0 (quindi nell’ordine naturale in cui siamo abituati a leggere le cifre, cioè da sinistra verso destra), e stampare il carattere 1 quando incontra un bit pari a 1, e un carattere 0 quando incontra un bit pari a 0.

L’intero programma quindi non fa altro che leggere dall’input un carattere alla volta e stamparne la rappresentazione binaria a 8 bit. Se in input scriviamo del testo, otterremo i valori in binario dei codici ASCII corrispondenti!

Piccolo esempio:

thekaneb@thekaneb-laptop:~/code/binarymessage$ echo “www.OScene.net” | ./bimessage
01110111 01110111 01110111 00101110 01001111 01010011 01100011 01100101 01101110 01100101 00101110 01101110 01100101 01110100

Grazie e alla prossima!

7 commenti
  1. Programmatore C
    Programmatore C dice:

    caspita, fica sta roba!
    però ad esempio a che cos’è che può servire?
    c’è un caso concreto in cui possa essere utile?
    tnx

    Rispondi
    • Antonio Barba
      Antonio Barba dice:

      @Programmatore C:
      serve in molti casi…. ad esempio per gestire un gruppo di flags binari, si usa raggrupparli in un’unica variabile intera, che prende il nome di bitfield. Per estrarre i valori, e settare i valori, all’interno dei bitfields si usano proprio le operazioni bitwise.

      Campi più concreti in cui si fa uso estensivo delle operazioni sui bit sono la compressione dati, la crittografia, la manipolazione di immagini e flussi multimediali, gli algoritmi genetici, ecc…

      Rispondi
  2. perer
    perer dice:

    Si, do ragione ad Alessandro, grandioso tutorial. Di meglio in internet sui Bitwise non si trova. Bravissimo. Diciamo che ho intuito a cosa servono, ma per saperlo veramente serve una padronanza assoluta della linguaggio di programmazione.
    Grazie mille 🙂

    Rispondi
  3. Duke
    Duke dice:

    Finalmente una spiegazione logica e sensata di questo argomento: ho solo trovato esempi campati in aria in giro, e finalmente qui si evince chiarezza

    Rispondi

Trackbacks & Pingbacks

  1. […] C scriveremmo qualcosa del genere (per chi non avesse padronanza con gli operatori binari, si rimanda al relativo […]

  2. […] correlatiCapire e sfruttare le operazioni bitwise (0)Imparare il Pascal: Hello World (4) In questo articolo vi mostrerò come sfruttare praticamente i […]

Lascia un Commento

Vuoi partecipare alla discussione?
Sentitevi liberi di contribuire!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *