Questo articolo prende spunto dal corso di Ingegneria degli Algoritmi e da un articolo di Steve Friedl sulla lettura delle dichiarazione di tipo.
Uno degli scogli principali per chi comincia a programmare in C è proprio quello di comprendere bene il significato dei vari * e delle funzioni più contorte. Non è inverosimile che per i principianti si sviluppi un metodo di programmazzione chiamato “proviamo e vediamo se funziona” che consiste nell’aggiungere asterischi a caso nella dichiarazione fino a quando il programma funziona come sperato.
Ovviamente questo metodo è sconsigliabile, contando che esiste una pratica regola che rende decifrabile anche le dichiarazioni più ingarbugliate: “go right when you can, go left when you must”
In italiano la regola suona più o meno: vai a destra finchè puoi, vai a sinistra quando devi. Ma come tutte le regole necessita di un esempio per essere afferrata.
Prendiamo una dichiarazione che farebbe impazzire chiunque:
char *(*(**foo [][8])())[];
“Oddio!” direte voi, ma manteniamo la calma. Il primo passo consiste nell’isolare il nome della variabile e il tipo base. Il nome della variabile è solitamente l’unica parola non riservata che trovate nella dichiarazione, nel nostro caso foo, mentre il tipo base è il tipo che si trova più a sinistra di tutti.
foo è un ... char
Ora dobbiamo riempire i puntini. Per prima cosa definiamo alcune semplici regole di lettura:
- gli asterischi * si leggono puntatori a o puntatore a
- le parentesi quadre [] si leggono array di
- le parentesi numerate [x] si leggono array di x di
- le parentesi tonde () si leggono funzione che restituisce
- le parentesi tonde con parametri (x,y) si leggono funzione che prende come parametro x, y e restituisce
Con queste regole ora non ci resta che partire a seguire la regola principale cominciando dal nome della dichiarazione.
La prima cosa che troviamo andando a destra sono le parentesi quadre, quindi:
char *(*(**foo [][8])())[]; foo è un array di ... char
Continuiamo ad andare a destra (ricordiamoci il “a destra finchè posso”) e troviamo altre parentesi quadre.
char *(*(**foo [][8])())[]; foo è un array di array di 8 di ... char
A questo punto troviamo una parentesi chiusa, non possiamo quindi più andare a destra finchè non troviamo, a sinistra, la corrispondente parentesi aperta. Facciamo quindi un balzo a sinistra e intanto continuiamo a riempire i puntini. A sinistra troviamo due asterischi quindi:
char *(*(**foo [][8])())[]; foo è un array di array di 8 di puntatori a puntatori a ... char
Abbiamo trovato la parentesi. Adesso quindi ci è permesso andare a destra. A destra troviamo un paio di parentesi tonde.
char *(*(**foo [][8])())[]; foo è un array di array di 8 di puntatori a puntatori a funzione che restituisce ... char
Altra parentesi chiusa. Andiamo quindi a sinistra.
char *(*(**foo [][8])())[]; foo è un array di array di 8 di puntatori a puntatori a funzione che restituisce puntatori a ... char
E cosi via finché non completiamo tutta la dichiarazione.
char *(*(**foo [][8])())[]; foo è un array di array di 8 di puntatori a puntatori a funzione che restituisce puntatori a array di... char
E cosi alla fine abbiamo che:
foo è un array di array di 8 di puntatori a puntatori a funzione che restituisce puntatori a array di puntatori a char.
Abbiamo quindi decriptato con facilità una dichiarazione che sembrava impossibile. Ora, per vedere se avete capito e se siete bravi vi lascio una dichiarazione per esercizio:
int * ( (* (* foo) () ) [5][7]) ()
Buon divertimento! 🙂