Perché il nome della funzione viene utilizzato come puntatore a funzione equivalente all’applicazione dell’operatore address-of al nome della funzione?

È interessante notare che l’ uso del nome della funzione come puntatore a una funzione equivale ad applicare l’indirizzo-dell’operatore al nome della funzione !

Ecco l’esempio.

typedef bool (*FunType)(int); bool f(int); int main() { FunType a = f; FunType b = &a; // Sure, here's an error. FunType c = &f; // This is not an error, though. // It's equivalent to the statement without "&". // So we have c equals a. return 0; } 

Usare il nome è qualcosa che già conosciamo nell’array. Ma non puoi scrivere qualcosa del genere

 int a[2]; int * b = &a; // Error! 

Sembra non coerente con altre parti della lingua. Qual è la logica di questo design?

Questa domanda spiega la semantica di un tale comportamento e perché funziona. Ma sono interessato al motivo per cui il linguaggio è stato progettato in questo modo.

La cosa più interessante è che il tipo di funzione può essere convertito implicitamente in puntatore a se stesso quando si usa come parametro, ma non verrà convertito in un puntatore a se stesso quando si usa come tipo di ritorno!

Esempio:

 typedef bool FunctionType(int); void g(FunctionType); // Implicitly converted to void g(FunctionType *). FunctionType h(); // Error! FunctionType * j(); // Return a function pointer to a function // that has the type of bool(int). 

Dal momento che chiedi espressamente il fondamento logico di questo comportamento, ecco la cosa più vicina che riesco a trovare (dal documento ANSI C90 Rationale – http://www.lysator.liu.se/c/rat/c3.html#3-3- 2-2 ):

3.3.2.2 Chiamate di funzione

I puntatori alle funzioni possono essere usati come (*pf)() o come pf() . Quest’ultimo costrutto, non sancito nel Documento di base, appare in alcune versioni attuali di C, non è ambiguo, non invalida alcun vecchio codice e può essere un’importante stenografia. La stenografia è utile per i pacchetti che presentano solo un nome esterno, che designa una struttura piena di puntatori a oggetti e funzioni: le funzioni membro possono essere chiamate come graphics.open(file) anziché (*graphics.open)(file) . Il trattamento dei designatori di funzioni può portare a forms curiose, ma valide, sintattiche. Date le dichiarazioni:

 int f ( ) , ( *pf ) ( ) ; 

quindi tutte le seguenti espressioni sono chiamate di funzioni valide:

 ( &f)(); f(); (*f)(); (**f)(); (***f)(); pf(); (*pf)(); (**pf)(); (***pf)(); 

La prima espressione su ciascuna linea è stata discussa nel paragrafo precedente. Il secondo è l’uso convenzionale. Tutte le espressioni successive sfruttano la conversione implicita di un designatore di funzioni in un valore puntatore, in quasi tutti i contesti di espressione. Il Comitato non ha visto alcun danno reale nel permettere queste forms; mettere fuori legge forms come (*f)() , pur permettendo *a (per int a[]) , sembrava semplicemente più un problema di quanto valesse.

Fondamentalmente, l’equivalenza tra i designatori di funzioni e i puntatori di funzione è stata aggiunta per rendere l’uso dei puntatori di funzione un po ‘più conveniente.

È una caratteristica ereditata da C.

In C, è permesso principalmente perché non c’è molto altro il nome di una funzione, da solo, potrebbe significare. Tutto ciò che puoi fare con una funzione reale è chiamarlo. Se non lo chiami, l’ unica cosa che puoi fare è prendere l’indirizzo. Poiché non c’è ambiguità, ogni volta che il nome di una funzione non è seguito da ( per indicare una chiamata alla funzione, il nome valuta l’indirizzo della funzione.

In realtà è in qualche modo simile a un’altra parte della lingua: il nome di una matrice valuta l’indirizzo del primo elemento dell’array tranne in alcune circostanze abbastanza limitate (essendo usato come operando di & o sizeof ).

Dato che C l’ha permesso, anche il C ++ lo fa, principalmente perché lo stesso rimane vero: l’unica cosa che puoi fare con una funzione è chiamarla o prendere il suo indirizzo, quindi se il nome non è seguito da a ( per indicare una chiamata di funzione , quindi il nome valuta l’indirizzo senza ambiguità.

Per gli array, non vi è alcun decadimento del puntatore quando viene utilizzato l’operatore address-of:

 int a[2]; int * p1 = a; // No address-of operator, so type is int* int (*p2)[2] = &a; // Address-of operator used, so type is int (*)[2] 

Questo ha senso perché gli array e i puntatori sono di tipo diverso, ed è ansible ad esempio restituire riferimenti ad array o passare riferimenti ad array in funzioni.

Tuttavia, con le funzioni, quale altro tipo potrebbe essere ansible?

 void foo(){} &foo; // #1 foo; // #2 

Immaginiamo che solo # 2 dia il tipo void(*)() , quale sarebbe il tipo di &foo essere? Non c’è altra possibilità.