Puntatori a Funzione in Linguaggio C
I puntatori a funzione in linguaggio C sono un concetto avanzato che permette di memorizzare l'indirizzo di una funzione in una variabile puntatore.
Questo consente di chiamare funzioni in modo dinamico, passare funzioni come argomenti ad altre funzioni e creare strutture dati complesse come tabelle di funzioni. Sebbene possa sembrare complicato inizialmente, comprendere i puntatori a funzione è fondamentale per sfruttare appieno la potenza e la flessibilità del linguaggio C.
In questa lezione ci concentreremo sulla sintassi e sulle operazioni di base che possiamo effettuare con i puntatori a funzione in linguaggio C.
Puntatori a Funzione
In linguaggio C, abbiamo visto che un puntatore può puntare a vari tipi di dato: variabili, array, stringhe, strutture dati e porzioni di memoria allocate in maniera dinamica.
In C, inoltre, è possibile creare anche puntatori che puntano a funzioni. A prima vista questo concetto può sembrare strano, ma in realtà, se ci pensiamo bene, anche le funzioni in C hanno un indirizzo di memoria. Quindi, possiamo creare un puntatore che punta a tale indirizzo.
Del resto, se riprendiamo lo schema della memoria di un programma in C, possiamo vedere che le funzioni sono memorizzate in una zona di memoria dedicata, chiamata segmento di Testo:
Le funzioni sono memorizzate nel segmento Text, hanno un proprio indirizzo e occupano un certo spazio in memoria. Quindi, possiamo creare un puntatore che punta a tale indirizzo.
La sintassi per creare un puntatore a funzione è leggermente più complessa rispetto a quella per creare un puntatore a variabile.
La prima cosa fondamentale da tenere a mente è che, così come un puntatore può puntare solo ad un tipo di dato specifico, così anche i puntatori a funzione possono puntare solo a funzioni che hanno lo stesso prototipo. Quindi, quando si crea un puntatore a funzione bisogna sapere esattamente due cose:
- Il tipo di ritorno della funzione;
- Il tipo e il numero di parametri che la funzione accetta.
Ad esempio, supponiamo di voler creare un puntatore che punti a funzioni che prendono in ingresso due interi e restituiscono un intero. La dichiarazione di un puntatore a funzione di questo tipo sarà la seguente:
int (*puntatore_a_funzione)(int a, int b);
A prima vista sembra che abbiamo definito un prototipo di funzione. Ciò che cambia è la presenza delle parentesi attorno al nome e all'asterisco. Questo è necessario per indicare che puntatore_a_funzione
è un puntatore a funzione.
Per il resto, la definizione di un puntatore a funzione è simile al prototipo di una funzione. I nomi dei parametri non sono necessari, ma possono essere inclusi per rendere il codice più leggibile. Inoltre essi non devono corrispondere necessariamente ai nomi dei parametri della funzione a cui il puntatore punta.
Puntatori a Funzione
Un Puntatore a Funzione in linguaggio C è una speciale variabile puntatore che è in grado di puntare all'indirizzo di memoria di una funzione.
Per dichiarare un puntatore a funzione, è necessario specificare il tipo di ritorno della funzione e i tipi dei parametri che la funzione accetta. La sintassi è la seguente:
tipo_di_ritorno (*nome_puntatore)(tipo1, tipo2, ..., tipoN);
Dove:
tipo_di_ritorno
è il tipo di dato restituito dalla funzione;nome_puntatore
è il nome del puntatore;tipo1
,tipo2
, ...,tipoN
sono i tipi dei parametri che la funzione accetta.
La sintassi prevede anche che si possano specificare dei nomi segnaposto per i parametri, ma non è obbligatorio.
Il vincolo dell'utilizzo di un puntatore a funzione è che esso può puntare solo a funzioni che hanno lo stesso prototipo.
Una volta dichiarato, vediamo come adoperare questo puntatore. Su di esso possiamo effettuare le operazioni che seguono.
Assegnare un indirizzo di funzione al puntatore
Riprendendo l'esempio precedente, supponiamo di avere due funzioni:
int somma(int a, int b) {
return a + b;
}
int prodotto(int a, int b) {
return a * b;
}
Queste due funzioni rispettano il prototipo richiesto dal puntatore puntatore_a_funzione
. Quindi possiamo assegnare l'indirizzo di una di queste funzioni al puntatore:
puntatore_a_funzione = somma;
Ora puntatore_a_funzione
punta alla funzione somma
.
Un piccolo dettaglio da tenere a mente è che, quando si assegna un indirizzo di funzione a un puntatore, non è necessario utilizzare l'operatore &
davanti al nome della funzione. Questo perché, come abbiamo detto, il nome di una funzione rappresenta già l'indirizzo di memoria della funzione.
In questo, la situazione è analoga al caso degli array dove il nome è esso stesso un puntatore all'indirizzo del primo elemento.
Assegnamento di un Indirizzo di Funzione a un Puntatore
La sintassi per assegnare un indirizzo di funzione a un puntatore è la seguente:
puntatore_a_funzione = nome_funzione;
Dove puntatore_a_funzione
è il puntatore a funzione e nome_funzione
è il nome della funzione a cui si vuole assegnare l'indirizzo.
Non è necessario utilizzare l'operatore &
davanti al nome della funzione, poiché il nome di una funzione rappresenta già l'indirizzo di memoria della funzione.
Un puntatore a funzione, così come un puntatore a variabile, può essere inizializzato anche al momento della dichiarazione:
int (*puntatore_a_funzione)(int a, int b) = somma;
Inoltre, possiamo anche inizializzare il puntatore a funzione a NULL
per indicare che non punta a nessuna funzione:
int (*puntatore_a_funzione)(int a, int b) = NULL;
Chiamare la funzione a cui il puntatore punta
Una volta assegnato l'indirizzo di una funzione al puntatore, possiamo chiamare la funzione attraverso il puntatore. Per fare ciò, possiamo usare la stessa sintassi che usiamo per chiamare una funzione:
int a = 10;
int b = 20;
int risultato1;
int risultato2;
puntatore_a_funzione = somma;
/* Chiamata alla funzione somma tramite il puntatore */
risultato1 = puntatore_a_funzione(a, b);
printf("Risultato della somma: %d\n", risultato1);
puntatore_a_funzione = prodotto;
/* Chiamata alla funzione prodotto tramite il puntatore */
risultato2 = puntatore_a_funzione(a, b);
printf("Risultato del prodotto: %d\n", risultato2);
In questo esempio, abbiamo chiamato la funzione somma
e prodotto
attraverso il puntatore puntatore_a_funzione
.
Quindi, risultato1
conterrà il valore 30
e risultato2
conterrà il valore 200
.
Esiste anche una seconda sintassi per chiamare una funzione attraverso un puntatore e spesso è usata per coerenza con la sintassi adoperata per usare puntatori a dati:
risultato1 = (*puntatore_a_funzione)(a, b);
In pratica, si racchiude il puntatore tra parentesi e si adopera l'operatore di dereferenziazione *
.
Scegliere tra le due sintassi è una questione di stile più che altro. Molti sviluppatori preferiscono la seconda per evidenziare il fatto che si sta chiamando una funzione attraverso un puntatore.
Chiamare una Funzione attraverso un Puntatore
Per chiamare una funzione attraverso un puntatore, possiamo usare la seguente sintassi:
risultato = puntatore_a_funzione(parametro1, parametro2, ..., parametroN);
Oppure, in alternativa:
risultato = (*puntatore_a_funzione)(parametro1, parametro2, ..., parametroN);
Dove puntatore_a_funzione
è il puntatore a funzione e parametro1
, parametro2
, ..., parametroN
sono i parametri che la funzione richiede.
Assicurarsi che il Puntatore a Funzione sia Inizializzato prima di Chiamare la Funzione
Così come per le variabili puntatore normali, anche per i puntatori a funzione valgono le stesse precauzioni.
In particolare dobbiamo assicurarci che il puntatore a funzione sia inizializzato prima di chiamare la funzione a cui punta. Se proviamo a chiamare una funzione attraverso un puntatore non inizializzato, il comportamento del programma sarà indefinito.
Confrontare due puntatori a funzione
Come per i puntatori a variabile, possiamo confrontare due puntatori a funzione. Due puntatori a funzione sono uguali se puntano alla stessa funzione. Ad esempio:
if (puntatore_a_funzione == somma) {
printf("Il puntatore punta alla funzione somma\n");
} else {
printf("Il puntatore non punta alla funzione somma\n");
}
In questo caso, il messaggio stampato sarà Il puntatore punta alla funzione somma
.
Sui puntatori a funzione non sono possibili altre operazioni. Non possiamo, ad esempio, applicare l'aritmetica dei puntatori o incrementare o decrementare un puntatore a funzione.
Confrontare due Puntatori a Funzione
Per confrontare due puntatori a funzione, possiamo usare gli operatori di confronto ==
, !=
.
Due puntatori a funzione sono uguali se puntano alla stessa funzione:
if (puntatore_a_funzione == nome_funzione) {
// I due puntatori puntano alla stessa funzione
}
Si possono anche usare gli altri operatori relazionali per confrontare due puntatori a funzione, <
, >
, <=
, >=
, ma il loro utilizzo non ha molto senso in questo contesto.
In Sintesi
In questa lezione abbiamo approfondito un costrutto fondamentale del linguaggio C: i puntatori a funzione.
Abbiamo visto che:
- Un puntatore a funzione è una variabile puntatore che può puntare all'indirizzo di memoria di una funzione;
- Per dichiarare un puntatore a funzione, è necessario specificare il tipo di ritorno della funzione e i tipi dei parametri che la funzione accetta;
- Ad un puntatore a funzione si possono assegnare gli indirizzi di funzioni che rispettano il prototipo richiesto;
- Una volta assegnato l'indirizzo di una funzione al puntatore, possiamo chiamare la funzione attraverso il puntatore;
- Due puntatori a funzione sono uguali se puntano alla stessa funzione.
I puntatori a funzione sono uno strumento potente che ci permette di scrivere codice più flessibile e generico. Nella prossima lezione ne vedremo una prima fondamentale applicazione: il passaggio di funzioni come argomenti ad altre funzioni.