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.

giovedì 20 dicembre 2012

La maledizione della Callback di giada
come scrivere una Callback in C

Oggi parleremo di un argomento un po' particolare, le funzioni callback. 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...).

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():
// funzione callback di comparazione per la qsort()
static int cbCmpFunc(const void *elem_a, const void *elem_b)
{
    return *(int*)elem_a > *(int*)elem_b;
}

// 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);

    // stampo 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):
/* 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)
{
    return elem_a > elem_b;
}

// 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);

    // stampo 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.

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

Ciao e al prossimo post.

mercoledì 28 novembre 2012

C'era una volta l'Ottimizzazione
come ottimizzare il codice C

To be, or not to be, that is the question: Whether 'tis Nobler in the mind to suffer... oops, forse ho sbagliato blog. Non é questo il blog di letteratura inglese? È un blog di programmazione C? Va beh, dato che ci sono, basta modificare un po' la domanda: Ottimizzare, o non ottimizzare, questo è il dilemma...

La domanda in realtà sarebbe: "quando scrivo codice devo farlo in maniera naturale, o meglio, istintiva (beh, sempre se si possiede l'istinto del programmatore...) o devo seguire linee un po' astruse ed ermetiche per rendere il programma più efficiente ?" E, ovviamente, non sto parlando di algoritmi: in questo caso è evidente che un buon algoritmo è preferibile a uno cattivo. Sto parlando a livello di istruzioni e gruppi di istruzioni.

Facciamo una premessa: siamo nel 2012 (quasi 2013). E di conseguenza dobbiamo comportarci. Anticamente i computer erano (rispetto a ora) lenti e con poche risorse, e i compilatori erano molto più semplici di quelli odierni. E, a quei tempi, usando la parola magica register era possibile velocizzare un loop e, se si riusciva a scrivere qualche linea di codice in meno (vedi nota esplicativa), si ottenevano programmi molto più performanti. Ma ora? Ha ancora senso scrivere codice così? Il compilatore ha veramente bisogno del nostro aiuto ?

nota esplicativa  
(...a 6 anni di distanza dalla pubblicazione ho scoperto, grazie alla segnalazione del mio ottimo collega Andrea Simone Costa, che l'espressione "...qualche linea di codice in meno..." si può facilmente fraintendere. Beh, come tutti ben sanno, l'efficienza del codice non è inversamente proporzionale al numero di linee scritte (e questo si può ben vedere sia negli esempi successivi di questo post sia in un post più recente che vi invito a leggere) ma dipende da ben altri fattori. Il senso della sfortunata espressione "...qualche linea di codice in meno..." è da intendere come: "...scrivere un codice ridotto all'osso omettendo qualsiasi operazione (apparentemente) superflua...", il che portava, nella migliore delle ipotesi, a scrivere in assembly le parti critiche e/o ad usare espressioni "criptiche" come quelle che vedremo tra poco (che miglioravano l'efficienza rendendo illeggibile il codice). E nella peggiore delle ipotesi... vabbe', potete immaginarvi cosa si poteva arrivare a fare per ridurre (in tutti i sensi) un programma che doveva girare su un vecchio sistema embedded con risorse inesistenti... Ecco, questo è quanto, spero che questa nota abbia chiarito l'equivoco...)

La faccenda è complessa, e credo che richiederà piú di una puntata. In questa, tanto per cominciare, analizziamo un esempio facile facile (?), l'ottimizzazione di un loop, che è, come noto, una parte (anzi, La Parte) critica per le prestazioni di un programma. Cominciamo:
// myFunc1()
// versione con moltiplicazione, array e indice
int myFunc1(int array[], int nelems)
{
    int i;
    for (i = 0; i < nelems; i++)
        array[i] = i * 2;
}
La funzione è semplicissima, però contiene un loop, e se nelems è molto grande potrebbe diventare dispendiosa. Proviamo a ottimizzarla usando due tecniche, la Strength Reduction e la Induction Variable: vediamo, attraverso vari passaggi cosa possiamo ottenere:
// myFunc2()
// versione con incremento invece di moltiplicazione
int myFunc2(int array[], int nelems)
{
    int i, incr;
    for (i = 0, incr = 0; i < nelems; i++) {
        array[i] = incr;
        incr += 2;
    }
}

// myFunc3()
// versione con incremento e pointer invece di array
int myFunc3(int array[], int nelems)
{
    int *ptr = array;
    int i, incr;
    for (i = 0, incr = 0; i < nelems; i++, ptr++) {
        *ptr = incr;
        incr += 2 ;
    }
}

// myFunc4()
// versione con incremento, pointer e senza indice
int myFunc4(int array[], int nelems)
{
    int *ptr = array;
    int limit = nelems * 2;
    int incr;
    for (incr = 0; incr < limit; incr += 2, ptr++)
        *ptr = incr;
}
Come promesso e premesso, in questo post non voglio limitarmi a proporre tecniche di ottimizzazione senza fornire dati, per cui ho scritto un programma di test e, sulla mia macchina, con un valore di nelems abbastanza grande (0xFFFFFFF) i risultati sono questi:
- compilazione senza ottimizzazione
myFunc1 - Tempo trascorso: 1.720000 secondi.
myFunc2 - Tempo trascorso: 1.340000 secondi.
myFunc3 - Tempo trascorso: 1.160000 secondi.
myFunc4 - Tempo trascorso: 0.920000 secondi.
Quindi, sembrerebbe che le tecniche di ottimizzazione funzionano. Proviamo, però, a compilare ottimizzando, ovvero lasciamo fare al compilatore: useremo la opzione -O2 di GCC. Vediamo i nuovi risultati:
- compilazione con ottimizzazione (-O2)
myFunc1 - Tempo trascorso: 0.940000 secondi.
myFunc2 - Tempo trascorso: 0.540000 secondi.
myFunc3 - Tempo trascorso: 0.540000 secondi.
myFunc4 - Tempo trascorso: 0.540000 secondi.
Ohhh, che sorpresa! Pare che il compilatore sia più bravo di noi! Usando l'opzione -O2, la versione non ottimizzata (myFunc1) si comporta, più o meno, come quella ottimizzata da noi (myFunc4) senza usare -O2. E non è una sorpresa: semplicemente vuol dire che il compilatore ha eseguito (probabilmente) le nostre stesse ottimizzazioni manuali. Notiamo poi che, usando una qualsiasi delle versioni ottimizzate manualmente, il compilatore riesce a aggiungere un ulteriore plus di miglioramento.

Tanto per confonderci ulteriormente le idee, facciamo un altro test, sostituendo la moltiplicazione per due con uno Shift Logico:
// myFunc2Bis()
// versione con shift logico invece di moltiplicazione
int myFunc2Bis(int array[], int nelems)
{
    int i;
    for (i = 0; i < nelems; i++)
        array[i] = i << 1;
}
E vediamo la velocità senza ottimizzazione del compilatore:
- compilazione senza ottimizzazione
myFunc2Bis - Tempo trascorso: 0.950000 seconds.
Toh, pare che usando lo shift logico da solo si ottiene lo stesso risultato che si ottiene applicando tutte le tecniche illustrate (myFunc4). E se usiamo anche l'opzione -O2 cosa succede ?
- compilazione con ottimizzazione (-O2)
myFunc2Bis - Tempo trascorso: 0.540000 seconds.
otteniamo lo stesso risultato ottimizzato visto sopra. Forse abbiamo scoperto il segreto del compilatore...

Prima conclusione (ma seguiranno altri capitoli): vale la pena di trasformare un codice leggibile e chiaro come quello di myFunc1() in uno molto più criptico, come ad esempio quello di myFunc4(), solo perché non ci fidiamo del compilatore? O, aggiungerei, perchè siamo così presuntuosi da pensare di poter fare meglio del compilatore? Mi ripeto: siamo nel 2012 (quasi 2013), e bisognerebbe comportarsi di conseguenza.

Per quello visto fin'ora, pare che l'unico aiutino manuale che possiamo dare è usare (quando possibile, e solo in punti veramente critici) shift logici invece di moltiplicazioni. In questo caso raccomando però l'aggiuntina di un commento, per evitare maledizioni da parte di chi legge il nostro codice. E se siete impazienti di scoprire altre utili informazioni sull'ottimizzazione, e non volete aspettare le mie prossime puntate (traditori!), vi consiglio di farvi il solito giro su Google, oppure di leggervi direttamente un ottimo articolo dell'altrettanto ottimo Carlo Pescio.

Io tornerò sull'argomento, cercando di aggiungere dati reali e lavoro di testing (visto che ho mantenuto la promessa!) agli studi solo teorici disponibili in rete. E se no che ci sto a fare io?

Ciao e al prossimo post.

domenica 18 novembre 2012

L'ultimo degli Apache II - La vendetta
come scrivere un modulo Apache in C - pt2

Ok, lo ammetto, il titolo è preoccupante, già che somiglia moltissimo a questo, ma, essendo la seconda parte di un discorso aperto, non mi è venuto in mente nulla di meglio. Forse sto invecchiando. E se in uno dei prossimi post mi auto-plagerò anche nel testo (oltre che nel titolo) segnalatemelo, così chiudo il blog prima che sia troppo tardi.

Comunque, come promesso, oggi vi spiegherò come scrivere, installare e provare un modulo Apache minimale. Così se qualcuno si appassiona all'argomento potrà, con questa base (funzionante!), divertirsi a scriverne di più complessi, alla faccia di quelli che "Il C non serve nello sviluppo Web".

Il nostro modulo basico lo chiameremo, ad esempio myapmod. Cominciamo con il codice, ovviamente:
#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>

// handler del modulo
static int myapmodHandler(
    request_rec *reqrec)
{
    // test handler
    if (! reqrec->handler || strcmp(reqrec->handler, "myapmod"))
        return DECLINED;

    // test metodo http
    if (reqrec->method_number != M_GET)
        return HTTP_METHOD_NOT_ALLOWED;

    // html output
    ap_set_content_type(reqrec, "text/html;charset=ascii");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n", 
                        reqrec);
    ap_rputs("<html>",  reqrec);
    ap_rputs("<head>",  reqrec);
    ap_rputs("<title>Modulo Apache Elementare</title>", reqrec);
    ap_rputs("</head>", reqrec);
    ap_rputs("<body>",  reqrec);
    ap_rputs("<h1>myapmod: il mio Modulo Apache elementare</h1>", 
                        reqrec);
    ap_rputs("</body>", reqrec);
    ap_rputs("</html>", reqrec);

    // esco con OK
    return OK;
}

// register hooks del modulo
static void myapmodHooks(
    apr_pool_t *pool)
{
    // set hook handler
    ap_hook_handler(myapmodHandler, NULL, NULL, APR_HOOK_MIDDLE);
}

// struttura globale del modulo
module AP_MODULE_DECLARE_DATA myapmod_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    myapmodHooks
};
Semplice no ? E ho inserito gli opportuni commenti nel codice (un po' stringati, devo ammetterlo) così non devo neanche spiegarvi come funziona. E, come noterete quando lo userete la prima volta, il codice HTML ivi contenuto viene correttamente visualizzato dal browser (ohhhh... miracolo).

Ora passiamo alla compilazione e installazione. Per comodità e gusti personali vi propongo una guida per Linux, ma vi assicuro che il modulo basico si può realizzare (se proprio volete soffrire) anche sotto Windows.

Il requisito preliminare è, ovviamente, aver installato Apache sul sistema. Cercate, con il nostro amico Google, una delle milioni di guide per trasformare il proprio pc in un server LAMP (o WAMP) (non vi descrivo io la procedura per non dilungarmi troppo, ma, vi garantisco, è abbastanza semplice). Una volta installato il server Apache assicuratevi che il vostro sistema sia anche preparato per compilare i moduli Apache: deve essere disponibile il pacchetto apache2-prefork-dev, quindi, per accertarvene, eseguite:
dpkg -l | grep apache2
E, se nella lista di pacchetti che vi verrà mostrata non c'è il pacchetto suddetto, installatelo con:
sudo apt-get install apache2-prefork-dev
E, adesso possiamo, finalmente, compilare e installare con:
sudo apxs2 -c mod_myapmod.c 
sudo apxs2 -i mod_myapmod.la
Poi, dobbiamo creare ed editare due nuovi file nella directory /etc/apache2/mods-available. Creiamo/apriamo il primo con:
sudo gedit /etc/apache2/mods-available/myapmod.load
e gli scriviamo dentro:
LoadModule myapmod_module /usr/lib/apache2/modules/mod_myapmod.so
Quindi creiamo/apriamo il secondo con:
sudo gedit /etc/apache2/mods-available/myapmod.conf
e gli scriviamo dentro:
<Location /myapmod>
SetHandler myapmod
</Location>
A questo punto non ci resta che abilitare il nostro nuovo modulo e far ripartire il Server Apache (che fará partite tutti i moduli abilitati, compreso il nostro myapmod):
sudo a2enmod myapmod
sudo /etc/init.d/apache2 restart
Se tutto é stato fatto correttamente, navigando con un browser alla url http://localhost/myapmod verrà visualizzato il codice HTML inserito nel nostro modulo. E se non funziona riprovate tutti i passi cominciando  dal primo, verificando che non vi sia sfuggito qualche errore segnalato dal sistema. Buona fortuna... (io, ovviamente, ho provato tutta la procedura prima di pubblicare il post, e sono sicuro che funziona).

Ovviamente con questo post non pretendo di scoprire l'acqua calda, visto che, se cercate su Internet come costruire un modulo Apache minimale, scoprirete che moltissimi altri hanno pubblicato ben prima di me guide analoghe. Ma volete mettere con leggere la stessa cosa scritta dal vostro C-blogger favorito?

Ciao e al prossimo post.

domenica 11 novembre 2012

L'ultimo degli Apache
come scrivere un modulo Apache in C - pt.1

Va bene: con un titolo come quello sopra, questo non può essere, sicuramente, il post (promesso) sull'ottimizzazione del codice. Ci sto ancora lavorando, e non so quando lo finirò, per cui, nel frattempo, pubblicherò qualche post più semplice. Ribadisco il consiglio di non trattenere il fiato in attesa di quel post (potrebbe passare molto tempo...).

Veniamo al dunque: anche questa volta cercherò di sfatare un mito (ci risiamo): quello del C e della programmazione Web.

Mi e capitato di sentir dire "Il C è un linguaggio universale, permette di fare quasi tutto (blah, blah, blah...) però nello sviluppo web non serve a nulla, lì servono altri linguaggi". Risata (controllata). Altra risata (lunga, questa volta).

Secondo me, in realtà, un (buon) programmatore C può entrare nel mondo Web per la porta grande: tanto per cominciare, buona parte dei linguaggi usati nello sviluppo Web sono C-like (ovvero i loro creatori si sono ispirati alla sintassi del C quando li hanno progettati): PHP, Java, C#, JavaScript, tanto per citarne solo alcuni. E se sono C-like saranno facilmente assimilabili da un esperto di C. E se il nostro esperto conosce (almeno un po') anche il C++ (cosa frequente tra i buoni programmatori C) anche il lato OOP dei linguaggi citati sarà facilmente interpretabile.

Quindi, un (buon) programmatore C può entrare nel mondo web molto rapidamente (non foss'altro per la forma mentis posseduta), e ve lo dico per esperienza personale, visto che ci sono passato anch'io (ma allora sto dicendo che sono un buon programmatore C ?). E, per puntualizzare e completare il discorso (e senza voler offendere nessuno), non credo che il passaggio inverso sia altrettanto semplice (un programmatore Web puro che si trasforma rapidamente in un buon sviluppatore C). Su questa mia ultima affermazione sono, comunque, molto aperto a commenti, obiezioni e impressioni diverse dalle mie. Magari mi sbaglio.

Uscendo, poi, dal discorso del linguaggio puro e duro, passo all'argomento applicativo (e, così, spieghiamo anche il misterioso titolo di questo post): il web vive su Apache e Apache vuol dire C. È noto che circa il 70% dei server web sono basati su Apache (è un dato di fatto), e, anche se è vero che e possibile lavorare a lungo nel mondo Web senza mai scrivere un modulo Apache, è anche vero che qualcuno deve aver pur scritto i moduli esistenti, e, prima o poi (e per svariati motivi), qualcuno avrà bisogno di un modulo ad-hoc per una attività particolare o dovrà toccare il codice di un modulo già esistente: in quel caso ci vogliono buoni programmatori C. Anche nel mondo Web. E ho detto tutto.

Nel mio prossimo post  vi presenterò un modulo Apache minimale (ce l'ho già quasi pronto), cosi vedremo in pratica cosa significa quanto detto sopra.

Ciao e al prossimo post.

mercoledì 31 ottobre 2012

Still Alive

Nel caso che qualche anima buona si sia preoccupata della mia (relativamente) lunga assenza da queste pagine, voglio rassicurarla: I'm still alive (suona bene in inglese, vero?).

Il fatto è che scrivo in questo blog nel mio tempo libero, e, a volte, non ne ho.

Un altro fatto, ancora più importante, è che mi piacerebbe sempre scrivere cose interessanti (da scrivere e, soprattutto, da leggere) e, a volte, mi manca l'ispirazione.

L'ultimo fatto (si, proprio l'ultimo, così non si annoia nessuno) è che, a volte, trovi un buon argomento ma ti rendi conto che, per descriverlo bene, ci vuole molto lavoro preparatorio (tipo scrivere un programma di test complesso) e, a causa del fatto n.1 (vedi sopra), i tempi si dilatano...

Va bene: attualmente sto scrivendo un post (secondo me) interessante, di cui (attenzione: spoiler) vi anticipo il tema: ottimizzazione del codice. Esiste una ampia letteratura al riguardo, ma quanti se la sono letta attentamente ? E quanti esempi reali (test con risultati) sono descritti in letteratura? Beh, visto che le leggende urbane fioccano sull'argomento, e spesso sono condite con consigli di dubbio risultato, ho pensato di presentare un po di dati reali (spero) indiscutibili.

E, già chi ci sono, vi racconterò (udite, udite !) una piccola disavventura che ho avuto, personalmente, sull'argomento.

Restate in attesa fiduciosi, ma, intanto, non trattenete il respiro, perché sono ancora un po' indietro...

Ciao e al prossimo post.

martedì 9 ottobre 2012

C'era una volta la malloc()
come usare la malloc() in C

Oggi parleremo di allocazione dinamica della memoria. Se facciamo una rapida ricerca con Google sulla famigerata malloc() e sul suo uso (provare con "why use malloc", per esempio), noteremo una notevole quantità di interrogativi sull'argomento che vanno dai semplici dubbi ("ma come si usa ?") ai dubbi esistenziali ("perchè si usa ?"). Beh, ci troviamo su un argomento pseudo-filosofico dovuto al fatto che, effettivamente, è possibile programmare a lungo in C senza mai usare la malloc()... ma, in questo caso, stiamo usando bene il linguaggio? Mi dispiace ma la risposta è NO!

Facciamo prima un po' di chiarezza, però. Chiunque (incluso il sottoscritto) si trovi con la necessità di scrivere (molto) rapidamente un piccolo programma, per testare rapidamente qualcosa - chissà per una urgenza improvvisa durante qualche manutenzione software critica e urgentissima (da terminare per ieri) - come lo scrive ? Ovviamente usando a man bassa variabili automatiche, array sovradimensionati ("memoria ce n'è tanta"), magari qualche variabile statica e (orrore, orrore) variabili globali. Ecco, insomma, uno di quei programmi che già mentre lo scrivi lo battezzi "temp_qualcosa.c" perché sai che dovrai rifarlo da capo appena possibile o dovrai cancellarlo per la vergogna (mica che qualcuno lo legge per sbaglio).

Ma qui, di solito, parliamo di stile e di cose fatte bene: il C ha una potente e flessibile gestione della memoria (è un linguaggio con i puntatori !), e scrivere in C facendo finta di non saperlo è un errore. Attenzione però: questo non significa che bisogna usare sempre puntatori e malloc() ("se no non sei un buon programmatore"), anzi è vero il contrario. All'interno di una funzione le variabili automatiche saranno sempre (e giustamente) la maggioranza, anche perché (quando è possibile e corretto) usare lo stack invece del heap migliora le prestazioni del programma e, tra l'altro, lo rende più semplice da scrivere, leggere e manutenere. La allocazione dinamica della memoria è una operazione dispendiosa per il sistema operativo, aggiunge (evidentemente) complicazione al codice e aumenta la possibilità di errori (alzi la mano chi non si è mai dimenticato di usare la corrispondente free() di una malloc()).

Ma allora quando e perché usare la malloc()? Scusate la risposta un po' lapalissiana, ma io direi:

    1) quando è indispensabile
    2) quando è meglio

Vediamo il primo punto di La Palice:

Quando è indispensabile? La allocazione dinamica è indispensabile in almeno due casi: il primo è quello delle funzioni che ritornano un indirizzo. Vediamo un semplice esempio di funzione che duplica una stringa (ce n'è uno quasi identico sul K&R):
char *myStrdup(
    char *src)
{
    // esegue un duplicato di src (con spazio per il '\0')
    char *dest = malloc(strlen(src) + 1);
    if (dest != NULL)
        strcpy(dest, src);

    return dest;
}
Semplice, no? Inutile dire (ma lo dico lo stesso) che, in una funzione come questa, si deve usare malloc() e non si può optare per usare l'indirizzo di un array automatico allocato sullo stack, che, proprio perché è sullo stack, si perde dopo il return.

Il secondo caso con scelta obbligata è quello delle linked list. Vediamo un esempio molto semplificato (ma perfettamente compilabile e testabile):
// nodo di una linked list singola con campo dati
typedef struct snode {
    int data;
    struct snode *next;
} node_t;

// alloca un node con i dati el un puntatore al prossimo elemento
node_t *addNode(
    node_t *next,
    int    data)
{
    node_t *node = malloc(sizeof *node);
    node->data = data;
    node->next = next;
    return node;
}

// inserisce un nodo in testa a una lista
void insertNode(
    node_t **head,
    int    data)
{
    node_t *node = addNode(*head, data);
    *head = node;
}

// main() per test
void main() 
{
    // lista vuota
    node_t *head = NULL;

    // inserisce 10 nodi con data=indice loop
    int i;
    for (i = 1; i <= 10; i++)
        insertNode(&head, i);

    // scorre la lista e stampa i valori
    while (head) {
        printf("%d\n", head->data);
        head = head->next ;
    }
}
Per chi non le avesse mai usate, le linked-list non sono una frivolezza: sono una delle costruzioni più potenti della programmazione in generale (e del C in particolare). Chi lavora intensamente in C su progetti grandi e professionali finirà, prima o poi, con usarle: un motivo in più, quindi, per familiarizzare con la allocazione dinamica della memoria.

Passiamo al secondo punto di La Palice:

Quando è meglio? Sicuramente tutte le volte che si devono maneggiare dei dati (tipicamente array di tipi semplici o array di strutture), di cui non si conosce a compile-time la dimensione: se non usassimo la malloc() bisognerebbe allocare array sovradimensionati (per evitare che manchi spazio a run-time). E quest'ultimo dettaglio ci indica un'altro punto: se dobbiamo maneggiare grosse quantità di dati non possiamo confidare troppo nello stack, il cui spazio non è infinito, anche se lavoriamo con moderni OS su macchine piene di memoria (e se programmiamo applicazioni embedded con forti limitazioni Hardware... ancora peggio).

Con quanto detto fin'ora spero, almeno, di aver contribuito a fare un po' di chiarezza su quest'argomento controverso. Ah, nel post ho citato sempre, per semplicità, la malloc(), ma, come ben tutti sanno, per l'allocazione dinamica della memoria c'è una vera famiglia di funzioni (malloc(), calloc(), realloc() e free()) che permettono una notevole flessibilità e varietà di soluzioni.

Beh, con questo è tutto, e ricordate: per ogni malloc() c'è una free(): se i conti non vi tornano cominciate a preoccuparvi...

Ciao e al prossimo post.

domenica 23 settembre 2012

Variabili globali? No, Grazie
come usare le variabili globali nel C

Oggi cercheremo di demolire un altro dei capisaldi della non-programmazione: la variabile globale. 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.

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;

void incrementaMyVar()
{
    my_var += 5;
}

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;

void faccioMilleCose()
{
    // faccio mille cose...
    // ...

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

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

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

    // chiamo faccioMilleCose()
    faccioMilleCose();    // oops! ho incrementato my_var per sbaglio

    // 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 di bisogno di spiegarlo, una globale è tutto meno che incapsulata... oops, ma questo è OOP, quindi esula un po' l'argomento del blog: scusatemi, volevo strafare. Beh, visto che siamo in argomento OOP, e quindi C++, cito volentieri il grande M.Cline che nelle su C++FAQ dice (traduco, eh):
Il nome di una variabile globale deve iniziare con //.

Ecco il modo ideale di dichiarare una variabile globale:

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

Ecco il modo ideale di usare una variabile globale:

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

OK, questo è un gioco. Una specie di. 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 malvage. 
Quindi lavatevi le mani dopo averle usate. Due volte.
Sante parole.

Comunque, se pensate che tutto questo sia solo farina del mio sacco provate a chiedere al nostro amico Google: global variable are evil? e vedrete che valanga di risultati viene fuori. Se vi interessa una delle pagine più interessanti la trovate qui.

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, bisogna farlo. La salute prima di tutto.

Ciao e al prossimo post.

domenica 9 settembre 2012

No Comment 2 - La vendetta
come scrivere i commenti nel C - pt.2

Oggi è venuto il momento di completare il discorso del No Comment che trovate qui. Avevamo, per così dire, parlato a livello micro (i commenti ai blocchi di codice), ma abbiamo tralasciato il livello macro, e cioè il contenitore stesso (il file) e i grossi blocchi di codice che sono le funzioni. Ditemi la verità: se lavorate (o avete lavorato) in team, quante volte avete aperto un file scritto da un collega (appartenente alla religione No Comment, devoto e praticante) e avete scoperto che l'unico indizio sul codice contenuto era il nome del file stesso ? Poi, visto che per la Legge di Murphy, l'esimio collega era in ferie (giuste e meritate), avete impiegato un giorno intero solo per capire a cosa servisse quel file, e, solo a quel punto, avete incominciato a decifrare il codice grazie alla Stele di Rosetta.

Un file ha bisogno di un header, e le funzioni (almeno quelle importanti, che di solito sono quelle globali) hanno bisogno di una intestazione fatta come si deve (comunque anche le funzioni statiche si meritano un minimo di presentazione). Spero che il messaggio sia chiaro.

Va bene, e allora come lo facciamo un header ? Premettendo che le varianti possibili sono infinite, direi che, per cominciare, anche qualcosa semplice semplice, come questa, potrebbe andare:
/*!
 *  FILE
 *     getComments.c - estrazione commenti da un sorgente C
 *  PROJECT
 *     myDoc - generazione automatica documentazione
 *  FUNCTIONS
 *     globali:
 *        getComments()
 *     statiche:
 *        formatLine()
 *  AUTHOR
 *     A.Abate
 *  OPERATING SYSTEM
 *     Linux (all versions)
 *  COMPILER
 *     GNU gcc
 *  RELEASE
 *     1.0 (Settembre 2012)
 */ 

Evidentemente si può abbondare più o meno con le descrizioni. Per esempio, se il file contiene il codice di un protocollo di comunicazione, si potrebbe aggiungere una descrizione a blocchi (un disegno in modo text) con le entità implicate e i flussi di comunicazione dei messaggi.

Per quanto riguarda le funzioni importanti, si può usare la classica sintassi delle Man Pages di Unix (e Linux), e quindi fare qualcosa del genere:
/*!
 *  NAME
 *     getComments - esamina un sorgente C per estrarre i commenti
 *  SYNOPSIS
 *     int getComments(
 *        char *inpath,
 *        char *oupath)
 *  DESCRIPTION
 *     Estrae i commenti da un sorgente C con pathname <inpath> e li
 *     scrive in un file di documentazione con pathname <outpath>.
 *  RETURN VALUE
 *     0 a operazione effettuata. Un valore negativo in caso di
 *     errore.
 */ 
E, anche qui, si può abbondare più o meno con le descrizioni, aggiungendo, per esempio, dettagli su tutti i codici di ritorno, e, se necessario, inserendo un ulteriore appartato EXAMPLES, con brevi esempi d'uso.

Come i più osservatori avranno notato, la prima riga dei due esempi qui sopra comincia con "/*!", ossia contiene un simbolo di riconoscimento per un eventuale tool di auto-generazione della  documentazione (tipo Doxygen). E, sempre i più osservatori, avranno notato che anche il contenuto dei due esempi parla di auto-documentazione. Il fatto è che ho pensato di proporre un caso reale, e cosa c'è di meglio che scrivere, ad-hoc, un semplicissimo tool di documentazione che ho scritto e testato in pochissimo tempo (è veramente molto semplice, ma si può perfezionare).

Beh, il mini-tool sarebbe (quasi) tutto qua:
int getComments(
    char *inpath,
    char *oupath)
{
    char *line = NULL;
    size_t len = 0;
    int retcode;
    bool start_comment = false;

    // apro il file di input
    FILE *fpin;
    if (fpin = fopen(inpath, "r")) {
        // apro il file di output
        FILE *fpout;
        if (fpout = fopen(oupath, "w")) {
            // leggo il file di input
            while (getline(&line, &len, fpin) != -1) {
                // test fine commenti per reset flag start_comment
                if (strstr(line, "*/") && start_comment) {
                    start_comment = false;
                    fprintf (fpout, "\n\n\n");
                }

                // test flag start_comment
                if (start_comment) {
                    // formatto e scrivo linea su file output
                    fprintf(fpout, "%s", formatLine(line));
                }

                // test inizio commenti per set flag start_comment
                if (strstr(line, "/*!"))
                    start_comment = true;
            }

            // libera risorse e set retcode=OK
            fclose(fpout);
            free(line);
            retcode = 0;
        }
        else
            retcode = -2;    // fopen err: set retcode=err

        fclose(fpin);    // libera risorse
    }
    else
        retcode = -1;    // fopen err: set retcode=err

    // esce con il retcode settato
    return retcode;
}
Direi che il codice è così intuitivo (e commentato) che non c'è molto da aggiungere: si cerca il codice di riferimento (/*!) e si copia in un file di documentazione tutto quello che lo segue fino a fine commento. La funzione formatLine() (che ho omesso) può non fare nulla (ossia restituire direttamente la linea passata), o fare qualche elaborazione più o meno sofisticata (come minimo raccomando di togliere gli inizi linea del tipo " *"). Io, per testare l'applicazione, ho scritto un main() che ricorre tutti i sorgenti di una directory e scrive i risultati in un altra directory (e, direi, funziona bene).

Quella presentata è una alternativa rapida ai vari tool tipo Doxigen, che sono, ovviamente, raccomandabili per grossi progetti (ma anche l'alternativa artigianale descritta non è male).

Chiudo con un messaggio per quelli che pensano (come già fatto notare qui) che scrivere i commenti (e, a maggior ragione, headers e descrizioni di funzioni) sia tempo perso che allunga i tempi di sviluppo. Bisogna avere larghezza di vedute, ragionare a lunga scadenza: anche se aumento del 10% i tempi di sviluppo (e documentare non richiede molto tempo, specie se lo fai mentre scrivi il codice), verrà il giorno in cui si recupererà con gli interessi il tempo perso in partenza, per esempio alla prima occasione di manutenzione o modifica (provate a farlo su un codice No Comment, poi mi raccontate...). E il tempo è denaro.

Ciao e al prossimo post.

venerdì 31 agosto 2012

return, o non return, questo è il dilemma
come strutturare il codice in C

Aveva ragione lui, a volte è difficile prendere delle decisioni esistenziali, e oggi ve ne propongo una io: singolo o multiplo punto di uscita ? Va bene, voi direte, "ci sono cose più importanti a cui pensare". Certamente, ma qui si tratta di stile, e, come già fatto notare qui, lo stile è importante. Del resto questa decisione da prendere non è una mia invenzione, ne ha parlato molto prima di me uno molto più bravo di me, anche se già ai tempi c'era chi non era completamente d'accordo.

Penso che il dilemma sia chiaro: stiamo parlando di Programmazione Strutturata mica di quisquilie. E se qualcuno sta pensando "ma la programmazione strutturata è superata", oppure "con la OOP l'approccio è completamente diverso", lo fermo subito. Qui si parla del C, il padre (o perlomeno lo zio) di tutti i linguaggi moderni, e il C è un linguaggio strutturato. Punto e basta. E se pensate che il C e la Programmazione Strutturata sono superati vi conviene prendervi una pausa di riflessione...

E allora ? Va beh, vediamo un esempio così ci chiariamo le idee. Scriviamo una funzione che apre un file, estrae la prima linea, processa i dati della linea ed esce. Come sempre si potrebbe scrivere in varie migliaia di modi diversi, ma noi isoliamo i due che ci servono per l'esempio (beh, isoliamo anche un terzo modo, ma quello è per finire in bellezza il post). Vediamo prima il tipo Single-Exit:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;
    int retcode;

    // apro il file in argomento
    FILE *fp;
    if (fp = fopen(path, "r")) {
        // leggo il file
        if (getline(&line, &len, fp) != -1) {
            // test contenuto file
            if (!strncmp(line, "output=", 7)) {
                // processa dati
                // ...

                retcode = 0;    // proc. OK: set retcode=OK
            }
            else
                retcode = -3;   // str err: set retcode=err

            free(line);     // libera risorse
        }
        else
            retcode = -2;   // getline err: set retcode=err

        fclose(fp);     // libera risorse
    }
    else
        retcode = -1;   // fopen err: set retcode=err

    // esce con il retcode settato
    return retcode;
} 

E adesso possiamo passare a analizzare il suo perfetto equivalente in modalità Multiple-Exit, ossia:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // apro il file in argomento
    FILE *fp;
    if ((fp = fopen(path, "r")) == NULL) {
        // esce con errore
        return -1;
    }

    // leggo il file
    if (getline(&line, &len, fp) == -1) {
        // libera risorse ed esce con errore
        fclose(fp);
        return -2;
    }

    // test contenuto file
    if (strncmp(line, "output=", 7)) {
        // libera risorse ed esce con errore
        fclose(fp);
        free(line);
        return -3;
    }

    // processa dati
    // ...

    // proc. OK: libera risorse ed esce con OK
    fclose(fp);
    free(line);
    return 0;
} 

Quale è il tipo migliore ? Beh, per me (e lui) è il tipo Single-Exit, ma, c'è da dire che il partito del Multiple-Exit è numeroso e formato (anche) da gente molto preparata.

E allora dove sta la verità, forse nel mezzo ? No, dire sempre che verità sta nel mezzo è la scusa degli indecisi. Io scelgo la soluzione con lo stile migliore, la più bella e facile da leggere, e, aggiungo, quella che ti dà un controllo maggiore mentre la scrivi: nel tipo Single-Exit è più difficile dimenticarsi di liberare le risorse giuste nel momento giusto (pensate a un programma più complesso, non all'esempio). E, inoltre, il tipo Single-Exit è anche più facile da leggere e debuggare (provare per credere, ma sempre con un caso più complesso).

Potrei fare un eccezione: quando con il tipo Single-Exit il livello di annidamento diventa troppo alto forse si potrebbe fare un ibrido: mettere i test basici (tipo "il parametro passato è nullo ?") all'inizio con i relativi return, e poi seguire in modo Single-Exit (ma, se alla fine il codice risultante non vi convince, magari è meglio se spezzate in due la funzione).

Comunque, sono meno radicale di quello che sembra: tra il codice che ho scritto nei miei trascorsi c'è (basta guardare qui) del codice Multiple-Exit o ibrido. Il fatto è che bisogna essere un po' elastici: programmare (bene) è un'arte, e troppe restrizioni non giovano all'espressività.

Come preannunciato chiudiamo in bellezza con il terzo tipo: anche la funzione seguente funziona, ed è un perfetto equivalente delle precedenti: 
int readFile3(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // faccio tutto in una botta sola
    FILE *fp;
    if ((fp = fopen(path, "r")) && (getline(&line, &len, fp) != -1) &&
            !strncmp(line, "output=", 7)) {

        // processa dati
        // ...

        // dati processati: libera risorse ed esce con OK
        fclose(fp);
        free(line);
        return 0;
    }
    else {
        // libera risorse ed esce con errore
        if (fp)
            fclose(fp);

        if (line)
            free(line);

        return -1;
    }
}

Ecco, se vi viene in mente di scrivere una funzione in quest'ultima maniera (magari mettendo ancora più test nello stesso if) rilassatevi, prendetevi un periodo di riposo, leggete qualche libro interessante e poi riprovateci con la testa finalmente sgombra da cattivi pensieri: magari (spero) non vi verrà più in mente di scriverla così. 

Ciao e al prossimo post.