2010-05-08 4 views
54

Perché e in che modo il dereferenziamento di un puntatore a funzione non fa "niente"?Come avviene il dereferenziamento di un puntatore a funzione?

Questo è ciò di cui sto parlando:

#include<stdio.h> 

void hello() { printf("hello"); } 

int main(void) { 
    (*****hello)(); 
} 

Da un commento su here:

puntatori a funzione dereference solo bene, ma la risultante funzione di designatore sarò subito convertito in un puntatore di funzione


E da una risposta here:

Dereferenziare (nel senso che pensate) puntatore di una funzione significa: l'accesso a una memoria codice come sarebbe un memoria dati.

Il puntatore di funzione non deve essere interpretato come in questo modo. Invece, si chiama .

Vorrei utilizzare un nome "dereferenziazione" lato a fianco con "chiamata". Va bene.

Comunque: C è stato progettato in modo tale che entrambi Identificatore funzione così come puntatore di funzione di tenuta variabile significa lo stesso: indirizzo CODE memoria. E consente di passare alla memoria utilizzando la sintassi call() o su un identificatore o una variabile.


Come esattamente fa dereferenziazione di un'opera puntatore a funzione?

risposta

44

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 rvalue è ovunque un nome o altro riferimento appare dove dovrebbe essere usato come un valore, piuttosto che una posizione — praticamente ovunque tranne sul lato sinistro di un'assegnazione. Il nome stesso deriva dal right -hand side 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 dereferenzia 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 si può provare:

  • Cosa succede se si dereference un puntatore a funzione in un lvalue contesto — il lato sinistro di una cessione. (La risposta riguarderà 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 allo elemento tipo, 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 aiuti.

P.S. Per quanto riguarda perché un valore di funzione viene convertito implicitamente in un puntatore, la risposta è che per quelli di utilizzo che usano i puntatori di funzione, è una grande comodità non dover utilizzare & 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.

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

+0

Potresti per favore approfondire "... implicitamente convertito in un puntatore al valore della funzione originale"? Ti riferisci al valore di ritorno della funzione? In tal caso, vuol dire che il compilatore mantiene automaticamente il valore di ritorno come un lvalue, sebbene il valore di ritorno della funzione fosse un valore. Grazie! – shorttermmem

5

C++ 03 §4.3/1:

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

Se si tenta un'operazione non valida su un riferimento funzione, come il * operatore unario, la prima cosa che la lingua cerca è una conversione standard. È proprio come convertire uno int quando lo si aggiunge a un float. L'utilizzo di * su un riferimento di 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 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 lo fa lavoro nella direzione opposta.

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

variabili di riferimento Funzione sono belli perché (in teoria, non ho mai provato) suggerimento per il compilatore che un ramo indiretto può essere inutile, se inizializzato in un ambito che racchiude.

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

Una designazione di funzione è un'espressione con tipo di funzione. Tranne quando è l'operando dell'operatore sizeof o dell'operatore unario &, una designazione di funzione con tipo "funzione che restituisce il tipo" viene convertita in un'espressione che ha il tipo "puntatore alla funzione che restituisce il tipo".

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

+0

"_un riferimento di funzione_" In realtà, ** un'espressione non può avere un tipo di riferimento **. Un'espressione può essere rvalore o lvalue. – curiousguy

2

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

Cosa si fa quando il programmatore trascina un puntatore a 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.

+4

Se fossi lo scrittore di compilatori, lo renderei illegale. Questa è una risposta un po 'fuorviante. – rpjohnst

1

In che modo funziona esattamente il dereferenziamento di un puntatore a funzione?

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

Nella prima fase, il compilatore vede ha un puntatore e un contesto in cui tale puntatore viene dereference (come (*pFoo)()) quindi genera il codice per quella situazione, codice che verrà utilizzato nel passo 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 esplicita CALL [register]. Su tali sistemi, un puntatore a 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].