Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

lunedì 8 aprile 2019

Callback Lucky
come funzionano le Callback in C

(...una premessa: questo post è un remake di un mio vecchio post (anzi, di due). L'ho riadattato e "modernizzato" per pubblicarlo su quell'altro bel blog collettivo dove scrivo. Visto che le modifiche sono molte e (forse) interessanti, lo ripubblico anche qui. Questo potrebbe ripetersi in futuro...)
Jimmy Logan: Noi abbiamo bisogno di un mago del computer.
Fish Bang: So tutto quello che c'è da sapere sui computer, okay? Tutti i Twitters, io li conosco.
Nel bel Logan Lucky c'è un momento di Metacinema in cui si cita il titolo di un altro film (diciamo) della stessa famiglia, e cioè Ocean's Eleven. Ecco, per fare un parallelo, le callback sono il Metacinema del C, perché lavorano in un universo parallelo, quello in cui si usa (si cita) una cosa che funziona e basta, il come importa fino a un certo punto. Orbene: è giunto il momento di squarciare il velo di mistero sulle callback...
...ragazzi, vi è chiaro ora come funzionano le callback?...

Cominciamo: è evidente che le le funzioni callback sono un argomento un po' particolare fin dall'origine. Perché particolare? Beh, tanto per cominciare, sulla bibbia del C (il K&R) non se ne parla mai, per cui un C-ista radicale potrebbe anche affermare che "le callback non esistono!". In realtà nel K&R si parla ampliamene degli zii delle callback, e cioè i puntatori a funzione (di cui le callback sono un caso particolare): quindi le callback esistono.

Non ho intenzione di scrivere un trattato di uso delle callback (sento già i vostri sospiri di sollievo), né di spiegare come, quando, perché e (soprattutto) se usarle: in rete ci sono molte fonti interessanti e ben scritte. Eventualmente sarebbe una buona cosa scrivere un post sugli zii, ma, visto l'argomento ostico e il probabile mal di stomaco che mi verrebbe durante la scrittura, lo rimando a data futura (tanto per intenderci: perfino Kernighan & Ritchie parlando dei Pointers to Functions hanno intitolato il capitolo 5.12 del K&R "Complicated Declarations", e se erano complicate per loro due...).

Di cosa parleremo allora? Beh, io (come molti altri, immagino) ho usato varie volte le callback e ho letto codice che le usava (codice che era, il più delle volte, illeggibile e di difficile interpretazione, in rapporto direttamente proporzionale alla frequenza d'uso delle callback, sigh). Ebbene, fino al giorno in cui ho scritto una implementazione completa (e cioè ho scritto, oltre alla callback, anche la funzione a cui passarla) non mi sono reso conto di alcuni dettagli nascosti. Ovviamente, se per voi le callback non hanno dettagli nascosti, potete saltare la lettura del seguito e arrivederci al prossimo post.

Siete ancora qua? Ok, prima di cominciare vediamo una definizione delle callback presa pari pari dalla Wikipedia: "una funzione che viene richiamata da un'altra funzione" e aggiungo io: "e, spesso e/o normalmente, la funzione chiamante è una funzione di libreria". L'esempio più classico e familiare che si cita in letteratura è relativo all'uso della qsort():
#include <stdio.h>
#include <stdlib.h>

// funzione callback di comparazione per la qsort()
static int cbCmpFunc(const void *elem_a, const void *elem_b)
{
    // ritorno il risultato della comparazione
    return *(int*)elem_a > *(int*)elem_b;
}

// funzione main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // eseguo sort array con qsort()
    qsort(array, nelems, sizeof(int), cbCmpFunc);

    // mostro i risultati
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);

    printf("\n");

    // esco
    return 0;
}
La qsort() è una funzione della libc che implementa l'algoritmo di ordinamento quicksort, e necessita di una funzione di callback che gli specifichi il tipo di ordinamento voluto. Siamo, quindi, in un caso classico: qsort() è una funzione di libreria, e noi, localmente, la usiamo passandogli una callback che, nel nostro esempio, serve per ordinare in modo ascendente i numeri dell'array.

E ora veniamo al dunque: in un altra epoca, quando non era facile come ora reperire documentazione ed esempi, mi era capitato di leggere e scrivere codice come quello mostrato sopra e mi domandavo (magari solo inconsciamente): "ma da dove saltano fuori i due parametri elem_a e elem_b della cbCmpFunc()?" e ancora: "Se io chiamo la callback e non gli passo esplicitamente parametri, come funziona il tutto?" Beh, come scoprii dopo, stavo ragionando rovesciando il rapporto causa-effetto: non ero io che chiamavo la callback, era la qsort() che la chiamava! Ok, mi vergogno un po' a raccontare una scoperta così lapalissiana, ma, effettivamente, lo compresi a fondo solo il giorno in cui ebbi la necessità di scrivere una funzione di libreria che usava una callback. Certo, ora con tutte le informazioni in rete è molto più facile...

Quindi, per chiarire, vediamo un esempio completo (N.B. l'esempio si potrebbe scrivere tutto in un file, ma, come evidenziato negli opportuni commenti, dovrebbe essere diviso su tre file in un progetto reale):
#include <stdio.h>

/* parte che dovrebbe essere nel file mysort.h
*/
// prototipi per mySort()
typedef int (*mycallback)(int, int);
void mySort(int *array, int nelems, mycallback cmpFunc);

/* parte che dovrebbe essere nel file mysort.c
 */
// mySort() - funzione di sort che usa l'algoritmo bubblesort
void mySort(int *array, int nelems, mycallback cmpFunc)
{
    // loop su tutti gli elementi di array
    while (nelems > 0) {
        // loop interno con lunghezza calante
        int i;
        for (i = 0; i < (nelems - 1); i++) {
            // eseguo callback di comparazione
            if (cmpFunc(array[i], array[i + 1])) {
                // eseguo swap di array[i] e array[i+1]
                int temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }

        // decremento nelems
        nelems--;
    }
}

/* parte che dovrebbe essere nel file mymain.c
*/
// cbCmpFunc() - funzione di comparazione
static int cbCmpFunc(int elem_a, int elem_b)
{
    // ritorno il risultato della comparazione
    return elem_a > elem_b;
}

// funzione main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // eseguo sort array
    mySort(array, nelems, cbCmpFunc);

    // mostro i risultati
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);

    printf("\n");

    // esco
    return 0;
}
Come vedete è molto simile all'esempio della qsort(), però, invece di usare una funzione della libc, usa una funzione di ordinamento scritta ad-hoc, la mySort(). Per quest'esempio ho usato, per non complicare troppo il codice, un algoritmo di tipo bubblesort, che è (lo so) un po' una schifezza, ma per fare un test semplice basta e avanza. Come noterete è la mySort() che si occupa di scrivere gli argomenti per la callback, processando opportunamente gli altri parametri che le vengono passati (array e nelems), e così, magicamente, appaiono dei valori in elem_a ed elem_b.

E visto che vogliamo abbondare (ma si, Punto! Due punti!... ma sì, fai vedere che abbondiamo... Abbondandis in abbondandum...) vi propongo un piccolo (e utile) trucco: supponiamo di avere bisogno di passare un altro parametro alla callback, un parametro esterno disponibile solo a livello della chiamata base, e che la mysort() non può generare internamente.

Come possiamo fare? Vediamo subito il nuovo codice (presentato come un blocco unico, ma, di nuovo, da dividere su tre file):
#include <stdio.h>

/* parte che dovrebbe essere nel file mysort.h
*/
// prototipi per mySort()
typedef int (*mycallback)(int, int, void *);
void mySort(int *array, int nelems, mycallback cmpFunc, void *newarg);

/* parte che dovrebbe essere nel file mysort.c
 */
// mySort() - funzione di sort che usa l'algoritmo bubblesort
void mySort(int *array, int nelems, mycallback cmpFunc, void *newarg)
{
    // loop su tutti gli elementi di array
    while (nelems > 0) {
        // loop interno con lunghezza calante
        int i;
        for (i = 0; i < (nelems - 1); i++) {
            // eseguo callback di comparazione
            if (cmpFunc(array[i], array[i + 1], newarg)) {
                // eseguo swap di array[i] e array[i+1]
                int temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }

        // decremento nelems
        nelems--;
    }
}

/* parte che dovrebbe essere nel file mymain.c
*/
// cbCmpFunc() - funzione di comparazione
static int cbCmpFunc(int elem_a, int elem_b, void *newarg)
{
    // scrivo risultati parziali in un file
    FILE *fp = (FILE *)newarg;
    fprintf(fp, "%d > %d = %d\n", elem_a, elem_b, elem_a > elem_b);

    // ritorno il risultato della comparazione
    return elem_a > elem_b;
}

// funzione main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // eseguo sort array scrivendo i risultati parziali in un file
    FILE *fp = fopen("result.txt", "w");
    mySort(array, nelems, cbCmpFunc, fp);

    // chiudo il file
    fclose(fp);

    // mostro i risultati
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);

    printf("\n");

    // esco
    return 0;
}
Come vedete è molto simile all'esempio precedente, solo che ora, a livello del main(), apriamo un file per registrare dei dati di elaborazione e dobbiamo passare il file descriptor alla mysort(), già che non possiamo certo pensare di scrivere una funzione di libreria che porti schiantato nel codice il nome del file di log: è l'applicazione chiamante che deve passarlo.

E come lo passiamo? È abbastanza semplice: si aggiunge un parametro (del tipo opportuno) alla mysort() e alla callback, e, nella chiamata base (nel main(), nel nostro caso) si passa il valore alla mysort(), che si occuperà i propagarlo fino alla callback, che è l'utilizzatrice del nuovo parametro; la mysort() non lo usa, lo trasporta solamente. Con questo metodo possiamo passare tutti i parametri che vogliano: nell'esempio ne ho aggiunto uno, ma se ne possono aggiungere a piacere.

Ovviamente tutto quanto sopra è valido per funzioni che implementiamo noi: non si può certo pensare di aggiungere parametri a funzioni di libreria di cui non abbiamo il controllo: ad esempio la qsort() ha bisogno solo della callback con due parametri, e così ce la dobbiamo tenere.

Qualcuno si chiederà perché il parametro newarg è un void* e non un tipo più specifico (in questo caso un FILE*): l'ho scritto così per dimostrare che, con questo metodo, si può passare qualsiasi cosa (ad esempio in C++ si usa per passare il pointer this): anzi, ho visto codice in cui si aggiungono dei void* alle callback (in fase di progetto) solo per usi futuri, in maniera di poter scrivere, in seguito, delle callback molto personalizzate senza modificare la funzione base (che, come detto, e solo trasportatrice di questi parametri).

Che ve ne sembra? Si, tutto un po' lapalissiano e ingenuo (forse), ma alzi la mano chi non ha mai avuto dubbi nella sua storia di programmatore (uhmm... vedo poche mani alzate).  E se questo post è servito a ampliare le conoscenze anche a solo uno dei lettori sono stra-contento così. E per gli altri, quelli che sapevano già tutto sulle callback: perché siete arrivati ugualmente fino in fondo al post ?

Ciao e al prossimo post!

mercoledì 13 marzo 2019

1997: Fuga da Bitwise
Come funzionano le operazioni di Bitwise in C

(...una premessa: questo post è un remake di un mio vecchio post (anzi, di due). L'ho riadattato e "modernizzato" per pubblicarlo su quell'altro bel blog collettivo dove scrivo. Visto che le modifiche sono molte e (forse) interessanti, lo ripubblico anche qui. Questo potrebbe ripetersi in futuro...)
Bob Hauk: Mi ucciderai ora, Jena?
Jena (Snake) Plissken: Sono troppo stanco... Forse più tardi...
Nel capolavoro 1997: Fuga da New York il grande John Carpenter ci mostrava come scappare da una tristissima New York trasformata in prigione federale. Ecco, l’argomento di questo articolo è uno di quegli argomenti che inducono alla fuga molti valorosi Programmatori C/C++… “ehm… oggi non ho tempo, puoi occupartene tu delle operazioni di bitwise? Poi domani mi farai vedere…
...a me Bitwise non me l'ha mai detto nessuno...
Cominciamo con un piccolo gioco: alzi la mano chi ha scritto recentemente codice che usa le operazioni bit a bit. Oppure, alzi la mano, senza prima andare a rileggersi un manuale del C/C++, chi di voi sa usare e/o descrivere perfettamente le operazioni bit a bit. Uhm… vedo poche mani alzate. Il fatto è che le bitwise operations sono una di quelle parti del C/C++ un po’ misconosciute, di uso dubbio e infrequente, insomma una di quelle parti di cui, per mancanza di pratica ci si scorda.

E, oltretutto, devo dire che sulle operazioni di bitwise il fantastico K&R non è particolarmente chiaro e dettagliato, le tratta (come sono) come un argomento di nicchia, e non ti coinvolge con decine di divertenti esempi che ti aiutano a memorizzare definitivamente l’argomento, anzi, per quel che ricordo dell’ultima volta che lo lessi, sono un paio di pagine che scivolano via e che hai già dimenticato quando passi al prossimo capitolo. Beh anche la bibbia K&R (che io considero sacra) ha alcuni punti non proprio coinvolgenti.

Rinfreschiamo: gli operatori di bitwise (che operano sui singoli bit) sono:
"&"  AND
"|"  OR
"^"  XOR
"~"  NOT (complemento a 1)
"<<" SHIFT a sinistra
">>" SHIFT a destra  
     
N.B.:
- il NOT e' un operatore unario: opera su un solo argomento indicato sulla destra.
- gli shift sono operatori unari: operano su un solo argomento indicato sulla sinistra.
Adesso, senza dilungarci in noiosi sproloqui, passiamo a una piccola tabella e ad alcuni semplici esempi pratici. Ecco la tabella:
"&": il risultato è 1 se i due operandi valgono 1. Altrimenti 0.
"|": il risultato è 0 se i due operandi valgono 0. Altrimenti 1.
"^": il risultato è 1 se i due operandi sono diversi. Altrimenti 0.
"~": il risultato è 1 se l'operando vale 0. Se l'operando vale 1 il risultato è 0.
"<< n": il risultato è l'operando con tutti i bit spostati a sinistra di n posizioni. 
">> n": il risultato è l'operando con tutti i bit spostati a destra di n posizioni.
Ed ecco i semplici esempi pratici:
AND
int a = 74;       // 0 1 0 0 1 0 1 0
int b = 174;      // 1 0 1 0 1 1 1 0
int c = a & b;    // 0 0 0 0 1 0 1 0 risultato c=10

OR
int a = 74;       // 0 1 0 0 1 0 1 0
int b = 174;      // 1 0 1 0 1 1 1 0
int c = a | b;    // 1 1 1 0 1 1 1 0 risultato c=238

XOR
int a = 74;       // 0 1 0 0 1 0 1 0
int b = 174;      // 1 0 1 0 1 1 1 0
int c = a ^ b;    // 1 1 1 0 0 1 0 0 risultato c=228

NOT
int a = 74;       // 0 1 0 0 1 0 1 0
int b = ~a;       // 1 0 1 1 0 1 0 1 risultato b=181

SHIFT a sinistra
int a = 74;       // 0 1 0 0 1 0 1 0
int b = a << 2;   // 0 0 1 0 1 0 0 0 risultato b=296

SHIFT a destra
int a = 74;       // 0 1 0 0 1 0 1 0
int b = a >> 2;   // 0 0 0 1 0 0 1 0 risultato b=18
Notare che nelle operazioni di shift i bit nuovi che entrano a destra (nello shift a sinistra) valgono 0, e i bit nuovi che entrano a sinistra (nello shift a destra) valgono 0.
Notare anche che lo shift a destra equivale a una divisione per multipli di 2 (>>1  è una divisione per 2,  >>2  è una divisione per 4, ecc.), mentre lo shift a sinistra equivale a una moltiplicazione per multipli di 2 (<<1  è una moltiplicazione per 2, <<2  è una moltiplicazione per 4, ecc.). Queste operazioni di moltiplicazione e divisione sono molto veloci, e si potrebbe essere tentati a usarle per velocizzare il codice: beh, prima di farlo rileggetevi (o leggetevi) questo.

E aggiungo un avvertimento: in base alla dimensione del tipo del operando e alla presenza o meno del bit di segno, le moltiplicazioni e divisioni con shift possono dare risultati inaspettati. Anzi, andiamoci subito alle dolenti note,  togliamoci il pensiero: vediamo un esempio:
#include <stdio.h>

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 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 (74×4=296). Se usiamo però (caso 2) un char (8 bit) durante lo shift perdiamo un 1 e il risultato va in overflow (74×4=40 ??). Quindi attenzione!

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:
#include <stdio.h>

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");
}
semplice no? E la stessa operazione si può fare con una macro:
#include <stdio.h>

#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");
}
E poi, visto che lo stile è sempre molto importante, 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:
#include <stdio.h>

#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");
}
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.

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 (…ma questa è un’altra storia…).

Ciao e al prossimo post!

domenica 17 febbraio 2019

Variabili Globali? No, grazie!
come NON usare le variabili globali nel C

(...una premessa: questo post è un remake di un mio vecchio post. L'ho riadattato e "modernizzato" per pubblicarlo su quell'altro bel blog collettivo dove scrivo. Visto che le modifiche sono molte e (forse) interessanti, lo ripubblico anche qui. Questo potrebbe ripetersi in futuro...)
"L'unica sua dote era una specie di futile entusiasmo. In pratica sembrava che usasse l'archetto come una sega, straziando le corde al punto da condurre l'ascoltatore verso pericolosi stati di follia. Non aveva cognizione della natura dello strumento: provava a soffiarci dentro." (Mr. A.Torgman) [maestro di violoncello di Virgil Starkwell, intervistato].
In Prendi i soldi e scappa il maestro di violoncello di Virgil Starkwell ci descriveva come si sentivano gli sfortunati ascoltatori di Virgil (Woody Allen) durante le sue esibizioni. Ecco, io ho esattamente le stesse sensazioni quando leggo del codice che usa uno dei capisaldi della non-programmazione: le variabili globali. Intendiamoci: le variabili globali (come anche il goto, per esempio) fanno parte del linguaggio, quindi esistono: a volte è possibile e/o necessario usarle. Però, esattamente come il goto, è quasi sempre possibile farne a meno, con grandi benefici di stile (leggibilità e manutenibilità, soprattutto), e funzionalità (meno bugs): chiamalo poco.
...la variabile globale: lo strumento sbagliato nel posto sbagliato...
I punti critici sono moltissimi, ma, visto che non voglio scrivere né un poema, né un libro sull’argomento, ne ho isolati alcuni. Vediamo:

1) le variabili globali non sono thread-safe

Nella programmazione multithreading l'uso delle globali può generare problemi di malfunzionamento subdoli e difficili da trovare. Il caso classico sono le istruzioni di lettura/scrittura di una globale fuori da una zona critica, ossia (per esempio) senza la protezione di un mutex: in un grande progetto è sufficiente una (una sola!) dimenticanza, di questo tipo per creare tanti di quei mal di testa che ti passerà la voglia di usare le globali per il resto della tua vita di programmatore. Provare per credere. A questo punto uno potrebbe obbiettare: "ma io scrivo programmi normali, non multithreading". Va bene: premettendo (e chiarendo nel prossimo paragrafo) che un programma "normale" può essere considerato un programma multi-thread con un solo thread, ringrazio per l'obiezione, che mi serve giusto di spunto per illustrare il punto 2:

2) le variabili globali violano il principio di manutenzione/riutilizzazione del Software

Quando si scrive del codice professionalmente bisogna sempre pensare al lavoro in team, e quindi alle operazioni di manutenzione che potrebbero essere svolte da altri (e, a volte) dopo molto tempo: evidentemente un codice, vasto e pieno di globali, è difficile da manutenere come un codice No Comment (ricordate ?), perché la storia di una globale è poco comprensibile, potrebbe essere toccata in molti posti diversi da molte funzioni diverse, e, se per capire un pezzetto di codice bisogna aprire alcune decine di file... avete vinto un altro bel mal di testa! E non ne parliamo di riutilizzare del codice pieno di globali per un altro progetto: se non l'avete mai fatto provate almeno a immaginarvi la difficoltà. E non solo: torniamo al punto 1 (thread-safe): chi mi dice che del codice "normale" non lo debba riutilizzare (un giorno) in un progetto multithread ? Se il codice è thread-safe si può fare agevolmente, ma se ci sono delle globali di mezzo... beh, buon lavoro (e buona fortuna).

3) le variabili globali aumentano la difficoltà del debug e moltiplicano la probabilità di errori di programmazione

Per quanto riguarda il debug vi rimando al punto 2: se il valore di una variabile è difficile da seguire a livello di manutenzione lo sarà anche a livello di debug. E ci saranno anche più malfunzionamenti da debuggare (fantastico!), perché, oltre a tutti i possibili errori di codificazione ci aggiungiamo anche quelli di scope: provate questo codice:
int my_var = 0;   // globale!

// funzione che incrementa la globale
void incrementaMyVar()
{
    my_var += 5;
}

// funzione main
int main(int argc, char **argv)
{
    // faccio mille cose...
    // ...

    // incremento my_var
    incrementaMyVar();

    // faccio altre mille cose...
    // ...

    // definisco una "nuova" variabile my var e la uso
    int my_var = 2;     // ah, ah, ah: ridefinizione!
    // ...

    // faccio ancora mille cose...
    // ...

    // incremento e test my_var (oops! quale my_var?)
    incrementaMyVar();
    if (my_var == 2)
        formatMyHardDisk(); // uh, uh, uh: era quella sbagliata!
    // ...

    return 0;
}
Quello mostrato sopra era un problema di scope con ridefinizione locale (accidentale) di una globale. Il codice che segue è ancora più semplice, mostra una svista su un dettaglio importante:
int my_var = 0;    // globale!

// funzione lunghissima che fa un sacco di cose
void faccioMilleCose()
{
    // faccio mille cose...
    // ...

    // incremento my_var (sepolto tra mille istruzioni!)
    my_var += 5;

    // faccio altre mille cose...
    // ...
}

// funzione main
int main(int argc, char **argv)
{
    // faccio un po' di cose...
    // ...

    // chiamo faccioMilleCose()
    faccioMilleCose();  // oops! ho incrementato my_var senza accorgermene

    // faccio altre cose...
    // ...

    // test my_var
    if (my_var == 5)
        formatMyHardDisk(); // uh, uh, uh: ho sbagliato qualcosa?
    // ...

    return 0;
}
Bella storia, eh ?

4) le variabili globali violano il principio di incapsulamento delle variabili

Beh, questo non c’è nemmeno bisogno di spiegarlo, una globale è tutto meno che incapsulata… oops, ma questo è OOP, quindi esula un po’ l’argomento del post… ah, no: questo articolo vale sia per C che per C++ (ed anche altri linguaggi, direi…). Beh, visto che siamo in argomento OOP, e quindi C++, cito volentieri il grande M.Cline che nelle su C++FAQ dice (traduco, eh):
I nomi delle variabili globali dovrebbero iniziare con //. 
Ecco il modo ideale per dichiarare una variabile globale:  

// int xyz; <-la cosa che rende ideale questa globale è l'iniziale // 

Ecco il modo ideale per utilizzare una variabile globale:

void mycode() 
{ 
    ... 
    // fai_qualcosa_con(xyz); <-idem come sopra 
    ... 
} 

Ok, questo è un gioco. Una specie. La verità è che ci sono casi in cui le 
variabili globali sono meno peggio delle alternative - quando le globali 
sono il minore dei mali. Ma loro sono sempre malvagie. Quindi lavatevi le 
mani dopo averle usate. Due volte.
(Marshall Cline C++FAQ sezione 27.15)

Sante parole.

E, per restare in tema C++, aggiungo un piccolo appunto: una Duna, anche se ci attacchi un logo Ferrari resta, ahimè, una Duna (e non me ne vogliano i lettori che usano la Duna, eh!). Allo stesso modo se, per darti delle arie, chiami Singleton una Variabile Globale Sofisticata il risultato non cambia, puoi sofisticarla quanto vuoi (inizializzazione “lazy”, inizializzazione “eager”, inizializzazione “oggi-sono-indeciso-e-faccio-un-mix-di-lazy-e-eager”, ecc., ecc.) ma, sotto sotto, è sempre una schifezza di Variabile Globale (anche se un pelino più accettabile, devo ammetterlo).

(…e qua bisogna giocare d’anticipo:  so che la affermazione qui sopra avrà fatto saltare sulla sedia tutti gli adepti della religione di Design Patterns, si, quelli che la sera recitano le orazioni davanti alla foto della Gang of Four che tengono sul comodino. Ecco, ho il massimo rispetto per loro, per la Gang of Four e per il libro che ha, sicuramente, un grande valore tecnico (e chi sono io per negarlo?) ma non è da prendere tutto come oro colato…  E vi assicuro che molta gente ha scritto del buon Software anche prima dell’uscita di quel libro (ma come avranno fatto?). Ecco, quel libro è l’equivalente informatico de La corazzata Potëmkin del Cinema, bello fin che vuoi (è un capolavoro) ma ti induce facilmente valutazioni come quella che fece Fantozzi. Anzi, posso affermare che, ultimamente, considero che il vero grande valore aggiunto che ci fornisce il libro Design Patterns è quello dissuasivo: supponiamo che avete (o avrete) un/a figlio/a che vuole dedicarsi all’Informatica, ma voi pensate che studiando Giurisprudenza o Economia possa avere un futuro più luminoso. Ecco, fategli trovare (casualmente) una copia di Design Patterns sul letto dicendo “…Ho ritrovato in cantina questo mio vecchio libro… dagli una occhiata, perché presto sarà il tuo pane quotidiano”. È probabile che la mattina successiva, verso le 6 (meglio non aspettare) verrete svegliati da vostro figlio/a chiedendovi “Come hai detto che ci si iscrive a Giurisprudenza?”…) 

CONCLUSIONE

Che possiamo dire in conclusione? Sicuramente che, se pensate che tutto quanto detto qui sopra sia solo farina del mio sacco, potete provate a interrogare il nostro amico Google, chiedendo, ad esempio: “global variables are evil?“, e vedrete che valanga di risultati vi verrà fuori. Se vi può interessare una delle pagine più ben fatte la trovate qui.
 
A questo punto penso che quanto detto sia sufficiente. Evidentemente io sono un po’ prevenuto perché ho lavorato a lungo su software multithreading (e ho ancora un po’ di mal di testa…), però vi chiedo di fidarvi e di propagare il messaggio il più possibile. Si, lo so, dire a un programmatore inesperto (o a un programmatore stanco) “non usare le variabili globali!” è come dire a un bambino “non mangiare troppe caramelle!“: beh, anche se a malincuore, bisogna farlo: la salute prima di tutto.

Ciao e al prossimo post!