RISOLTO [C] puntatori a funzione

Programmazione

demda

Ciao a tutti, sto studiando i puntatori a funzione, ma devo ammettere che l'unica cosa che ho capito è che: sono dei puntatori che puntano all'inzio della funzione (cella di memoria di inizio della funzione) e che possono essere usati per chiamare la funzione. Non ho però capito:

1) L'utilità di questi puntatori, cioè perché dovrei utilizzarli quando posso fare le stesse cose chiamando la funzione in modo "canonico", mi sfugge qualcosa?

A questo punto mi sorge un'altra domanda, è possibile applicare l'aritmetica dei puntatori anche sui puntatori a funzione? Per puntare per esempio ad una determinata istruzione della funzione, o sto fantasticando ?

Grazie
 
Ultima modifica:
#1

DispatchCode

Staff Forum
Utente Èlite
Ciao, posso portarti un caso pratico dell'utilizzo di un puntatore a una funzione.
Due parole su ciò che vedrai: si tratta di un piccolissimo emulatore con meno del 50% delle istruzioni di una 8086. Ho "mappato" (è una jump table, in pratica) le istruzioni in un array, dove ogni cella dell'array (ogni indice) contiene l'indirizzo di una funzione diversa.
Quando devo emulare, ad esempio, l'opcode numero 20, non faccio altro che usare questo valore come indice dell'array e poi richiamare la funzione.
Per dare il contesto, ti linko il codice (alla riga della funzione che effettua quanto detto pocanzi): https://github.com/DispatchCode/NaTE/blob/master/src/cpu_exec.c#L935
L'utilità in questo caso è la scelta a runtime della funzione da chiamare in base a ciò che viene decodificato.

Un'altra possibile applicazione è una callback: richiami una funzione alla quale come parametro passi l'indirizzo di un'altra funzione.

Riguardo la tua ultima domanda: in pratica non sono applicabili le stesse regole.
Ogni funzione è composta da un certo numero di istruzioni, quindi al limite riesci a "puntare" alle singole istruzioni (ma dovrai "comprendere" quanto è lunga ogni istruzioni per puntare alla successiva).
Insomma, l'aritmetica di un puntatore è possibile in quanto si conosce il tipo di dato (int*, char*,...).
 
#2

Andretti60

Utente Èlite
Chiamare una funzione in modo "canonico", come dici tu, significa che la funzione da chiamare viene decisa in fase di compilazione, non può quindi essere cambiata durante l'esecuzione. Usando una funzione "generica" (ossia un puntatore) permette di scrivere codice che si può riusare. Prendi per esempio la funzione di sorting:
Codice:
void qsort (void* base, size_t num, size_t size, int (*comparator)(const void*, const void*));
la funzione che viene usata per fare il confronto non può naturalmente essere scritta all'interno della funzione di sorting, deve essere scritta a parte, la si scrive e poi e se ne passa il suo puntatore.

E ovviamente, come tu hai già immaginato, possono essere "manipolati" per accedere metodi di cui si conosce il loro offset rispetto a un indirizzo "base" "(aiuta usare l'operatore offsetof())

Ho cercato di stare nel più possibile generico e semplice in questa esposizione (ho cancellato buona parte di quello che avevo originariamente scritto). Chi usa il linguaggio C/C++ in modo avanzato non può fare a meno di usare puntatori a funzioni, tecnica che sta svanendo nei linguaggi più moderni dove i puntatori sono "spariti", tra virgolette perché esistono ancora "dietro le scene".
 
#3

demda ha detto:
Ciao a tutti, sto studiando i puntatori a funzione, ma devo ammettere che l'unica cosa che ho capito è che: sono dei puntatori che puntano all'inzio della funzione (cella di memoria di inizio della funzione) e che possono essere usati per chiamare la funzione. Non ho però capito:

1) L'utilità di questi puntatori, cioè perché dovrei utilizzarli quando posso fare le stesse cose chiamando la funzione in modo "canonico", mi sfugge qualcosa?

A questo punto mi sorge un'altra domanda, è possibile applicare l'aritmetica dei puntatori anche sui puntatori a funzione? Per puntare per esempio ad una determinata istruzione della funzione, o sto fantasticando ?

Grazie

Nello sviluppo di un algoritmo, i puntatori alla funzione sono utili nel momento in cui non è possibile determinare "a priori" quale funzione debba essere richiamata in determinate circostanze, ossia quando è possibile farlo soltanto al momento dell'esecuzione, in base all'analisi dei dati :sisi:
Un tipico esempio può essere quello in cui viene attuato un tipo di programmazione "a flussi", caratterizzata da tabelle in cui i dati di input permettono l'individuazione dell'elemento tabellare contenente i puntatori alle funzioni che possono essere richiamate in quel certo contesto ;)
 
Mi Piace: demda e fabio93
#4

Andretti60

Utente Èlite
Una delle situazione in cui i puntatori delle funzioni sono indispensabili in C/C++ e' quando si vuole definire delle funzioni denominate "callback".

Supponi che stai creando una libreria, e che tu abbia bisogno di essere notificato durante il suo funzionamento di "qualcosa" che sta accadendo al suo interno. Classici esempi:
  1. lo stato di una lunga operazione: magari chiami una funzione che impiega un lungo tempo per essere eseguita, quindi vuoi che magari ogni secondo ti avvisi quale e' la percentuale di lavoro che sta elaborando, cosi' puoi mostrare il progresso all'utente (mediante uno slider o una etichetta)
  2. un evento: supponi che sia una libreria grafica che crea un bottone, vuoi che tale libreria ti notifiche quando l'utente ha cliccato il mouse su tale bottone.
Le librerie non sanno nulla di chi le stia usando, quindi non ha modo di chiamare nessuna funzione che tu abbia scritto se tu non le passi il puntatore.

Ti faccio un semplice esempio del primo caso.
C:
public void FaiQualcosa(void(*callback)(double percentage))
{
    for(int i=0; i<1000; i++)
    {
        // lunga operazione
        if (callback != null)
            (*callback)(100.0*i/1000.0);
    }
}
La funzione FaiQualcosa richiede un parametro: il puntatore di una funzione (void) con un parametro (double) che rappresenta la percentuale della operazione. Se tale puntatore non e' nullo, tale funzione viene chiamata. Questo e' il codice del cliente (di cui ricordo la libreria sopra NON ne fa parte):
C:
void Stampa(double d)
{
    printf("%.2lf%% eseguito\n", d);
}

void main()
{
    FaiQualcosa(Stampa);
}
Si definisce la funzione di "callback", la si usa come si vuole (in questo caso semplicemente scrive sulla console) e se ne passa il puntatore alla funzione FaiQualcosa()

Come vedi il codiceFaiQualcosa() e' estremamente "generico" che puo' essere chiamato da chiunque, in diverse condizioni. Se non si ha bisogno di notificazioni si passa il puntatore nullo, altrimenti quello di una funzione locale.
 
#5

demda

Andretti60 ha detto:
Chiamare una funzione in modo "canonico", come dici tu, significa che la funzione da chiamare viene decisa in fase di compilazione, non può quindi essere cambiata durante l'esecuzione. Usando una funzione "generica" (ossia un puntatore) permette di scrivere codice che si può riusare. Prendi per esempio la funzione di sorting:
Codice:
void qsort (void* base, size_t num, size_t size, int (*comparator)(const void*, const void*));
la funzione che viene usata per fare il confronto non può naturalmente essere scritta all'interno della funzione di sorting, deve essere scritta a parte, la si scrive e poi e se ne passa il suo puntatore.

E ovviamente, come tu hai già immaginato, possono essere "manipolati" per accedere metodi di cui si conosce il loro offset rispetto a un indirizzo "base" "(aiuta usare l'operatore offsetof())

Ho cercato di stare nel più possibile generico e semplice in questa esposizione (ho cancellato buona parte di quello che avevo originariamente scritto). Chi usa il linguaggio C/C++ in modo avanzato non può fare a meno di usare puntatori a funzioni, tecnica che sta svanendo nei linguaggi più moderni dove i puntatori sono "spariti", tra virgolette perché esistono ancora "dietro le scene".

Cosa intendi quando dici che " la funzione da chiamare viene decisa in fase di compilazione, non può quindi essere cambiata durante l'esecuzione " ? non ti seguo
 
#6

Andretti60

Utente Èlite
demda ha detto:
Cosa intendi quando dici che " la funzione da chiamare viene decisa in fase di compilazione, non può quindi essere cambiata durante l'esecuzione " ? non ti seguo
se tu chiami la funzione con il suo nome, la chiamata viene "decisa" in fase di compilazione.
Per esempio l'istruzione y=sin(x) calcolera' sempre il seno del numero x, mentre invece l'istruzione y=(*func)(x) calcolera' il valore di x in base a cio' che la variabile func puntera' in quel momento.
 
#7

demda

Grazie mille a tutti, mi siete stati d'aiuto
 
Mi Piace: Andretti60
#8

pabloski

Utente Èlite
demda ha detto:
Grazie mille a tutti, mi siete stati d'aiuto

Tieni presente che nei linguaggi "più avanzati", le funzioni sono "first class", cioè vengono trattati come tutti gli altri tipi di dato. In pratica puoi invocare una funzione e passarle, tra i parametri, un'altra funzione. E' il meccanismo classico per implementare le "high order functions".

E' facile intuire che un meccanismo del genere dà una flessiblità incredibile.

E il tutto senza dover usare esplicitamente i puntatori ( che in moltissimi linguaggi di alto livello nemmeno esistono ).
 
Mi Piace: demda
#9

Andretti60

Utente Èlite
pabloski ha detto:
Tieni presente che nei linguaggi "più avanzati", le funzioni sono "first class", cioè vengono trattati come tutti gli altri tipi di dato ...
Vero, come per i delegate di C#
 
Mi Piace: demda
#10

demda

pabloski ha detto:
Tieni presente che nei linguaggi "più avanzati", le funzioni sono "first class", cioè vengono trattati come tutti gli altri tipi di dato. In pratica puoi invocare una funzione e passarle, tra i parametri, un'altra funzione. E' il meccanismo classico per implementare le "high order functions".

E' facile intuire che un meccanismo del genere dà una flessiblità incredibile.

E il tutto senza dover usare esplicitamente i puntatori ( che in moltissimi linguaggi di alto livello nemmeno esistono ).
Buono a sapersi, in futuro non si sa mai !

Comunque continuerò a preferire il C perché a mio parere ti obbliga a capire come funziona veramente un elaboratore (gestione memoria etc..)


Inviato da Mi A2 Lite tramite App ufficiale di Tom\'s Hardware Italia Forum
 
#11

pabloski

Utente Èlite
demda ha detto:
Comunque continuerò a preferire il C perché a mio parere ti obbliga a capire come funziona veramente un elaboratore (gestione memoria etc..)

Imho lo studio del C è fondamentale proprio per questo motivo. E altrettanto fondamentale è un'infarinatura di Assembly, perchè ci sono cose del calcolatore che pure il C astrae.

Sostanzialmente si può vivere senza conoscere come funziona il calcolatore a livello logico. Ma la conoscenza di quello che sta sotto il cofano è fondamentale per creare buon software, anche usando linguaggi ad alto livello.

Per esempio una cosa che si dà per scontata è l'allocazione della memoria ( anche in C ). Ma dietro ci sono meccanismi che, se conosciuti, possono aiutarci ad impostare i programmi in modo che siano performanti, lavorando in sinergia con gli allocatori di memoria, piuttosto che andargli contro.
 
Mi Piace: fabio93 e demda
#12
Stato
Discussione chiusa ad ulteriori risposte.