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.

12 commenti:

  1. Ciao,finalmente una bella spiegazione mettendosi nei panni del principiante che si fa domande ovvie.
    A volte sembra quasi che chi scrive gli articoli sottintenda cose che non sono ovvie per tutti.
    E' facile fare comprendere una cosa a chi ha un QI di 150,un pò meno farla comprendere a me,per esempio

    RispondiElimina
  2. Bellissimo post! Molto utile... Grazie Aldo

    RispondiElimina
    Risposte
    1. Grazie a te Gabriele. Sono contento che ti sia stato utile.

      Elimina
  3. Io avrei una domanda. La callback qui serve per dire se l ordinamento deve essere ascendente o discendente esatto? Ma allira perchè scomodare i puntatori a funzione e non usare un flag come parametro per dire come deve essere ul verso dell ordinamento?

    Un altra cosa. Io mi sono fatto l idea che le callback siano utili per fare una programmazione dinamica.

    Attendo tue notizie

    RispondiElimina
    Risposte
    1. È evidente che per fare una funzione di ordinamento semplice, tipo "ascendente e basta" non c'è bisogno di scomodare le callback. Ma se vogliamo fare una funzione più generica, con cui si possa scegliere dinamicamente il tipo di operazione interna da eseguire, le callback cascano a fagiolo. Del resto, come evidenziato dal post, quando si parla di callback il primo esempio che viene in mente è qsort() (che è una funzione della libc che non ho certo inventato io), che è, storicamente, legata all'uso di una callback per effettuare la comparazione.
      Se ti può interessare io sono tutt'altro che un fanatico delle callback, e le uso con molta parsimonia perché penso che "aiutano" a rendere il codice criptico e illeggibile. Ciao!

      Elimina
    2. Questo commento è stato eliminato dall'autore.

      Elimina
  4. Grazie Aldo,

    Grazie per la tua risposta. Condivido appieno quello che hai scritto. Infatti, l'idea che mi ero fatto è che le callbacks siano utili per fare una programmazione dinamica come hai detto tu e quindi me ne hai dato conferma.

    Complitenti per il blog. Mi piace molto e aiuti le persone a capire argomenti o aspetti della programmazione utili da usare

    RispondiElimina
  5. "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... "

    Ciao Aldo e complimenti per il blog. Non lo abbandonare mai. Io lo scopro solo ora.
    Posso chiedermi di linkare o aggiungere in coda delle fonti di approfondimento ? Io sono esattamente interessato (anche solo per 'cultura generale' sul come, quando, perchè e se usare le funzioni di callback ... In generale mi piacerebbe approfondire e tanto i puntatori a funzione e esserne a pieno padrone per scrivere codice più modulare. Non riesco a trovare fonti che mi possono formare a riguardo. Sui libri, anche più famosi è argomento tabù o al massimo accenno a dichiarazione/inizializzazione e uso elementare. Non mi basta, cerco esempi concreti che spiegano come utilizzarli al meglio...
    Grazie in anticipo e complimenti ancora!

    RispondiElimina
    Risposte
    1. Grazie per i complimenti!

      Come ho detto nell'articolo in rete si trova di tutto e di più sull'argomento, ma una vera "bibbia delle callback e dei function pointers" non c'è, o almeno io non l'ho vista. Ci sono molti articoli di buon livello, però, e leggendone un po' (uno non basta!) potrai aumentare le tue conoscenze.

      Io consiglio, sempre e comunque, di iniziare dalla base, e cioè dal K&R, e poi, semplicemente usando google, cercare in rete e, tra i mille articoli proposti, sbirciarne qualcuno e leggere con attenzione quelli che sembrano più interessanti (sicuramente i migliori li riconoscerai a prima vista).

      Comunque, ti posso consigliare due libri veramente interessanti che ti potrebbero tornare utili: "C Programming: Data Structures and Algorithms" [J.Straub] e "Object Oriented Programming with ANSI-C" [A.T.Schreiner].

      Buona lettura!

      Elimina