Come avviene il dereferenziamento di un puntatore a funzione?

Perché e in che modo il dereferenziamento di un puntatore a funzione non fa “niente”?

Questo è quello di cui sto parlando:

#include void hello() { printf("hello"); } int main(void) { (*****hello)(); } 

Da un commento qui :

i puntatori di funzione non funzionano correttamente, ma il designatore della funzione risultante verrà immediatamente convertito in un puntatore di funzione


E da una risposta qui :

Dereferenziazione (nel modo in cui si pensa) il puntatore di una funzione significa: accedere a una memoria CODE come se fosse una memoria DATI.

Il puntatore di funzione non è supposto per essere dereferenziato in questo modo. Invece, è chiamato.

Vorrei usare un nome “dereferenziato” accanto a “chiamata”. Va bene.

Ad ogni modo: C è progettato in modo tale che sia l’identificatore del nome della funzione sia il puntatore della funzione di mantenimento variabile significano lo stesso: indirizzo alla memoria CODE. E permette di saltare a quella memoria usando la syntax call () su un identificatore o variabile.


Come funziona esattamente il dereferenziamento di un puntatore a funzione?

Non è proprio la domanda giusta. Per C, almeno, la domanda giusta è

Cosa succede a un valore di funzione in un contesto di rvalue?

(Un contesto di rvalue è ovunque un nome o un altro riferimento appare dove dovrebbe essere usato come valore, piuttosto che come posizione, praticamente ovunque tranne sul lato sinistro di un compito. Il nome stesso deriva dal lato destro di un incarico.)

OK, quindi cosa succede a un valore di funzione in un contesto di rvalue? Viene convertito immediatamente e implicitamente in un puntatore al valore della funzione originale. Se si denomina quel puntatore con * , si ottiene di nuovo lo stesso valore di funzione, che viene convertito immediatamente e implicitamente in un puntatore. E puoi farlo tutte le volte che vuoi.

Due esperimenti simili puoi provare:

  • Cosa succede se si disgeenzia un puntatore a funzione in un contesto di lvalue : il lato sinistro di un compito. (La risposta sarà su ciò che ti aspetti, se tenga presente che le funzioni sono immutabili).

  • Un valore di matrice viene anche convertito in un puntatore in un contesto di lvalue, ma viene convertito in un puntatore al tipo di elemento , non a un puntatore all’array. Il dereferenziamento ti darà quindi un elemento, non un array, e la follia che mostri non si verifica.

Spero che questo ti aiuti.

PS Per quanto riguarda il motivo per cui un valore di funzione viene convertito implicitamente in un puntatore, la risposta è che per coloro che usano i puntatori di funzione, è una grande comodità non dover usare e ovunque. C’è anche una duplice convenienza: un puntatore di funzione in posizione di chiamata viene automaticamente convertito in un valore di funzione, quindi non è necessario scrivere * per chiamare attraverso un puntatore a funzione.

PPS A differenza delle funzioni C, le funzioni C ++ possono essere sovraccaricate e io non sono qualificato per commentare come la semantica funziona in C ++.

C ++ 03 §4.3 / 1:

Un lvalue del tipo di funzione T può essere convertito in un valore di rvalore di tipo “puntatore a T.” Il risultato è un puntatore alla funzione.

Se si tenta un’operazione non valida su un riferimento di funzione, ad esempio l’operatore unario * , la prima cosa che la lingua tenta è una conversione standard. È come convertire un int quando lo aggiungi a un float . L’uso di * su un riferimento a una funzione fa sì che la lingua assuma invece il suo puntatore, che nel tuo esempio è quadrato 1.

Un altro caso in cui ciò si applica è quando si assegna un puntatore a una funzione.

 void f() { void (*recurse)() = f; // "f" is a reference; implicitly convert to ptr. recurse(); // call operator is defined for pointers } 

Si noti che questo non funziona nell’altro modo.

 void f() { void (&recurse)() = &f; // "&f" is a pointer; ERROR can't convert to ref. recurse(); // OK - call operator is *separately* defined for references } 

Le variabili di riferimento delle funzioni sono piacevoli perché (in teoria, non ho mai testato) suggeriscono al compilatore che un ramo indiretto può non essere necessario, se inizializzato in un ambito che racchiude.

In C99, il dereferenziamento di un puntatore a funzione produce un designatore di funzione. §6.3.2.1 / 4:

Una designazione di funzione è un’espressione che ha un tipo di funzione. Tranne quando è l’operando dell’operatore sizeof o unario e operatore, una designazione di funzione con tipo ” function return type ” viene convertita in un’espressione che ha tipo ” puntatore a funzione che restituisce tipo ”.

Questo è più simile alla risposta di Norman, ma in particolare C99 non ha alcun concetto di rvalore.

Mettiti nei panni dello scrittore di compilatori. Un puntatore a funzione ha un significato ben definito, è un puntatore a un blob di byte che rappresenta il codice macchina.

Cosa fai quando il programmatore dereferisce un puntatore a una funzione? Prendi i primi (o 8) byte del codice macchina e lo reinterpretano come un puntatore? Le probabilità sono circa 2 miliardi a uno che non funzionerà. Dichiari UB? Un sacco di cose che vanno già in giro. O ignori semplicemente il tentativo? Tu sai la risposta.

Come funziona esattamente il dereferenziamento di un puntatore a funzione?

Due passi Il primo passo è in fase di compilazione, il secondo in fase di esecuzione.

Nel primo passo, il compilatore vede che ha un puntatore e un contesto in cui quel puntatore è dereferenziato (come (*pFoo)() ), quindi genera codice per quella situazione, codice che verrà usato nel passaggio 2.

Nel passaggio 2, in fase di esecuzione, il codice viene eseguito. Il puntatore contiene alcuni byte che indicano quale funzione deve essere eseguita successivamente. Questi byte sono in qualche modo caricati nella CPU. Un caso comune è una CPU con un’istruzione CALL [register] esplicita. Su tali sistemi, un puntatore di funzione può essere semplicemente l’indirizzo di una funzione in memoria e il codice di derefencing non fa altro che caricare quell’indirizzo in un registro seguito da un’istruzione CALL [register] .