Scrivere Software è un piacere. Un programma non solo deve funzionare bene ed essere efficiente (questo si dà per scontato), ma deve essere anche bello ed elegante da leggere, comprensibile e facile da manutenere, sia per l'autore che per eventuali lettori futuri. Programmare bene in C è un'arte.
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.
martedì 25 dicembre 2012
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():
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):
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.
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.
Iscriviti a:
Post (Atom)