Cominciamo dalle dolenti note: come anticipato nel post precedente, le moltiplicazioni e divisioni con shift, in base alla dimensione del tipo del operando e alla presenza o meno del bit di segno, possono dare risultati inaspettati. Vediamo un esempio:
Il caso 1 è, evidentemente il caso funzionante: un int è molto più grande degli 8 bit usati per rappresentare il numero di partenza (74), per cui lo shift non perde nessun 1 sulla sinistra (è questo il possibile problema) e il risultato è corretto (74x4=296). Se usiamo però (caso 2) un char (8 bit) durante lo shift perdiamo un 1 e il risultato va in overflow (74x4=40 ??). Quindi attenzione!void main() { // 1) SHIFT a sinistra con unsigned int unsigned int a, b; a = 74; // 0 0 1 0 0 1 0 1 0 b = a << 2; // 1 0 0 1 0 1 0 0 0 risultato b=296 printf("var = %d; var << 2 = %d\n", a, b); // 2) SHIFT a sinistra con unsigned char unsigned char c, d; c = 74; // 0 1 0 0 1 0 1 0 d = c << 2; // 0 0 1 0 1 0 0 0 risultato d=40 printf("var = %d; var << 2 = %d\n", c, d); // 3) SHIFT a destra con signed char (o int) char e, f; e = -4; // 1 1 1 1 1 1 0 0 f = e >> 2; // 1 1 1 1 1 1 1 1 risultato f=-1 printf("var = %d: var >> 2 = %d\n", e, f); // N.B.: senza estensione di segno sarebbe: // e = -4; // 1 1 1 1 1 1 0 0 // f = e >> 2; // 0 0 1 1 1 1 1 1 risultato f=63 }
Il caso 3, poi, è ancora più subdolo: facendo operazioni con segno (nei casi 1 e 2 ho usato variabili unsigned proprio in preparazione al punto 3) e usando valori negativi, possono succedere cose strane: per la rappresentazione stessa dei numeri negativi in binario (complemento a 2) il bit più a sinistra (MSB) è il bit di segno, e, in questo caso l'operazione di shift è machine-dependent: in base al tipo di CPU possiamo disporre o no dell'estensione di segno (di default, ad esempio, su macchine Intel), per cui lo shift dell'esempio può dare il risultato aspettato (-4/4=-1) o un risultato completamente diverso (-4/4=63 ??). Di nuovo: attenzione!
E adesso è il momento di mostrare alcuni esempi pratici di uso di quanto esposto, che altrimenti, sarebbe know-how fine a se stesso: vediamo come leggere lo stato dei singoli bit di una word usando una maschera:
semplice no? E la stessa operazione si può fare con una macro:void main() { // uso di una maschera per leggere i bit di una word unsigned char mask = 1; // 0 0 0 0 0 0 0 1 unsigned char word = 74; // 0 1 0 0 1 0 1 0 // loop di lettura int i; for (i = 0; i < 8; i++) printf("il bit %d della word è %s\n", i, (word & mask<<i) ? "ON" : "OFF"); }
E poi, visto che questo è un blog di stile, vediamo un modo con una una buona estetica per leggere degli input di un dispositivo, per esempio i fine corsa di un sistema elettromeccanico che dobbiamo controllare col nostro amato C:#define INPUT(w, i) (w & 0x01<<i) void main() { // uso di una macro per leggere i bit di una word unsigned char i_word = 74; // 0 1 0 0 1 0 1 0 // loop di lettura int i; for (i = 0; i < 8; i++) printf("il bit %d della word è %s\n", i, INPUT(i_word, i) ? "ON" : "OFF"); }
Ecco, l'esempio appena mostrato indica una maniera, semplice ed elegante, per descrivere degli input (usando dei mnemonici auto-esplicativi) che può risultare utile per scrivere del Software di controllo di dispositivi Hardware, facile da leggere e da manutenere.#define INPUT_FC1 (in_word & 0x01<<0) #define INPUT_FC2 (in_word & 0x01<<1) void main() { // uso di una define per ogni bit da leggere di una word unsigned char in_word = 74; // 0 1 0 0 1 0 1 0 // lettura printf("il bit FC1 della word è %s\n", INPUT_FC1 ? "ON" : "OFF"); printf("il bit FC2 della word è %s\n", INPUT_FC2 ? "ON" : "OFF"); }
Facile da leggere e da manutenere: questa l'ho già sentita... ah si: è come dovrebbe essere tutto il S/W che scrive un Analista Programmatore (e peccato che molti analisti programmatori con la minuscola si scordano di questo imperativo quando si siedono davanti a un computer...).
Ciao e al prossimo post!
Nessun commento:
Posta un commento