2009-10-19 20 views
175

Sono sempre stato un po 'perplesso quando ho letto il codice di altre persone che aveva typedef per i puntatori a funzioni con argomenti. Ricordo che mi ci è voluto un po 'per aggirare una tale definizione mentre cercavo di capire un algoritmo numerico scritto in C qualche tempo fa. Quindi, potresti condividere i tuoi suggerimenti e pensieri su come scrivere buoni typedef per i puntatori alle funzioni (Do e Do not's), perché sono utili e come capire il lavoro degli altri? Grazie!Comprensione dei typedef per i puntatori di funzioni in C

+1

Potete fornire qualche esempio? – Artelius

+2

Non intendi typedef per i puntatori di funzione, invece di macro per i puntatori di funzione? Ho visto il primo ma non il secondo. – dave4420

+0

Vedere anche [Come dichiarare un puntatore a funzione __stdcall] (http://stackoverflow.com/q/5298394). – jww

risposta

21

cdecl è un ottimo strumento per la decifrazione di dichiarazioni di puntatori a funzioni simili a quelle della sintassi. Puoi usarlo per generare anche loro.

Per quanto riguarda i suggerimenti per rendere le dichiarazioni complicate più facili da analizzare per la manutenzione futura (da soli o altri), raccomando di creare typedef s di piccoli pezzi e di utilizzare quei piccoli pezzi come elementi costitutivi per espressioni più grandi e più complicate. Per esempio:

typedef int (*FUNC_TYPE_1)(void); 
typedef double (*FUNC_TYPE_2)(void); 
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2); 

piuttosto che:

typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void); 

cdecl può aiutarti con questa roba:

cdecl> explain int (*FUNC_TYPE_1)(void) 
declare FUNC_TYPE_1 as pointer to function (void) returning int 
cdecl> explain double (*FUNC_TYPE_2)(void) 
declare FUNC_TYPE_2 as pointer to function (void) returning double 
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int 
int (*(*FUNC_TYPE_3)(double (*)(void)))(void) 

Ed è (di fatto) esattamente come ho generato quel casino pazzesco sopra .

+2

Ciao Carl, questo è stato un esempio e una spiegazione molto perspicace. Inoltre, grazie per aver mostrato l'uso di cdecl. Molto apprezzato. –

+0

C'è cdecl per windows? – Jack

+0

@Jack, sono sicuro che puoi costruirlo, sì. –

54

Un puntatore a funzione è come qualsiasi altro puntatore, ma punta all'indirizzo di una funzione anziché all'indirizzo dei dati (su heap o stack). Come ogni puntatore, deve essere digitato correttamente. Le funzioni sono definite dal loro valore di ritorno e dai tipi di parametri che accettano. Quindi, per descrivere completamente una funzione, è necessario includere il suo valore di ritorno e il tipo di ciascun parametro è accettato. Quando si digita una definizione simile, si fornisce un "nome descrittivo" che semplifica la creazione e il riferimento ai puntatori che utilizzano tale definizione.

Così, per esempio si supponga di avere una funzione:

float doMultiplication (float num1, float num2) { 
    return num1 * num2; } 

allora la seguente typedef:

typedef float(*pt2Func)(float, float); 

può essere usato per puntare a questa funzione doMulitplication. Si tratta semplicemente di definire un puntatore a una funzione che restituisce un oggetto mobile e prende due parametri, ciascuno di tipo mobile. Questa definizione ha il nome descrittivo pt2Func. Notare che pt2Func può puntare a QUALSIASI funzione che restituisce un valore float e occupa 2 valori.

in modo da poter creare un puntatore che indica la funzione doMultiplication come segue:

pt2Func *myFnPtr = &doMultiplication; 

e si può richiamare la funzione utilizzando questo puntatore come segue:

float result = (*myFnPtr)(2.0, 5.1); 

Questo rende buona lettura: http://www.newty.de/fpt/index.html

+0

psicotico, grazie! E 'stato utile. Il collegamento alla pagina web dei puntatori di funzione è davvero utile. Leggendolo ora. –

+0

... Tuttavia, questo collegamento newty.de non sembra parlare di typedef del tutto :( Quindi anche se quel link è ottimo, ma le risposte in questo thread su typedef sono inestimabili! –

+6

Si potrebbe voler fare ' pt2Func myFnPtr = & doMultiplication; 'invece di' pt2Func * myFnPtr = & doMultiplication; 'come' myFnPtr' è già un puntatore. – Tamilselvan

238

Considerare la funzione signal() dallo standard C:

extern void (*signal(int, void(*)(int)))(int); 

perfettamente obscurely ovvio - è una funzione che prende due argomenti, un intero ed un puntatore a una funzione che prende un intero come argomento e restituisce nulla, e (signal()) restituisce un puntatore a una funzione che prende un numero intero come argomento e non restituisce nulla.

se si scrive:

typedef void (*SignalHandler)(int signum); 

poi si può invece dichiarare signal() come:

extern SignalHandler signal(int signum, SignalHandler handler); 

Ciò significa la stessa cosa, ma è generalmente considerato come un po 'più facile da leggere. È più chiaro che la funzione accetta int e SignalHandler e restituisce un SignalHandler.

Ci vuole un po 'per abituarsi, però. L'unica cosa che non si può fare è scrivere una funzione di gestione del segnale usando SignalHandlertypedef nella definizione della funzione.

Sono ancora della vecchia scuola che preferisce richiamare un puntatore a funzione come:

(*functionpointer)(arg1, arg2, ...); 

sintassi moderna utilizza solo:

functionpointer(arg1, arg2, ...); 

posso capire perché che funziona - ho appena preferisco sapere che ho bisogno di cercare dove viene inizializzata la variabile piuttosto che per una funzione chiamata functionpointer.


Sam ha commentato:

ho visto questa spiegazione prima. E poi, come è il caso ora, penso che quello che non ho avuto era la connessione tra le due affermazioni:

extern void (*signal(int, void()(int)))(int); /*and*/ 

    typedef void (*SignalHandler)(int signum); 
    extern SignalHandler signal(int signum, SignalHandler handler); 

Oppure, quello che voglio porre è, qual è il concetto di base che si può usare a venire con la seconda versione che hai? Qual è il fondamento che collega "SignalHandler" e il primo typedef? Penso che ciò che deve essere spiegato qui è ciò che sta facendo tipicamente Typedef.

Proviamo di nuovo. Il primo di questi viene sollevato direttamente dallo standard C, l'ho ridigitato e ho verificato che avevo le parentesi giuste (non finché non l'ho corretto, è un cookie difficile da ricordare).

Prima di tutto, ricordare che typedef introduce un alias per un tipo. Quindi, l'alias è SignalHandler e il suo tipo è:

un puntatore a una funzione che accetta un numero intero come argomento e non restituisce nulla.

La parte "restituisce nulla" è digitata void; l'argomento che è un numero intero è (mi fido) auto-esplicativo. La seguente notazione è semplicemente (o meno) come C magie puntatore a funzione argomenti prendendo come specificato e restituzione del tipo indicato:

type (*function)(argtypes); 

Dopo aver creato il tipo di gestore di segnale, posso usarlo per dichiarare variabili e così via. Per esempio:

static void alarm_catcher(int signum) 
{ 
    fprintf(stderr, "%s() called (%d)\n", __func__, signum); 
} 

static void signal_catcher(int signum) 
{ 
    fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum); 
    exit(1); 
} 

static struct Handlers 
{ 
    int    signum; 
    SignalHandler handler; 
} handler[] = 
{ 
    { SIGALRM, alarm_catcher }, 
    { SIGINT, signal_catcher }, 
    { SIGQUIT, signal_catcher }, 
}; 

int main(void) 
{ 
    size_t num_handlers = sizeof(handler)/sizeof(handler[0]); 
    size_t i; 

    for (i = 0; i < num_handlers; i++) 
    { 
     SignalHandler old_handler = signal(handler[i].signum, SIG_IGN); 
     if (old_handler != SIG_IGN) 
      old_handler = signal(handler[i].signum, handler[i].handler); 
     assert(old_handler == SIG_IGN); 
    } 

    ...continue with ordinary processing... 

    return(EXIT_SUCCESS); 
} 

Si prega di notare How to avoid using printf() in a signal handler?

Allora, che cosa abbiamo fatto qui - a parte omettere 4 intestazioni standard che sarebbero necessari per rendere il codice compilato in modo pulito?

Le prime due funzioni sono funzioni che prendono un singolo numero intero e non restituiscono nulla. Uno di questi in realtà non ritorna affatto grazie allo exit(1); ma l'altro ritorna dopo aver stampato un messaggio. Tieni presente che lo standard C non ti consente di fare molto all'interno di un gestore di segnali; POSIX è un po 'più generoso in ciò che è permesso, ma ufficialmente non sanziona chiamando fprintf(). Inoltre, stampo il numero del segnale che è stato ricevuto. Nella funzione alarm_handler(), il valore sarà sempre SIGALRM in quanto è l'unico segnale per il quale è un gestore, ma signal_handler() potrebbe ottenere SIGINT o SIGQUIT come numero di segnale perché la stessa funzione è utilizzata per entrambi.

Quindi creo un array di strutture, in cui ogni elemento identifica un numero di segnale e il gestore da installare per quel segnale. Ho scelto di preoccuparmi di 3 segnali; Mi preoccupo spesso di SIGHUP, SIGPIPE e SIGTERM e se sono definiti (compilazione condizionale #ifdef), ma ciò complica le cose. Probabilmente userò anche POSIX sigaction() invece di signal(), ma questo è un altro problema; Rimaniamo con ciò che abbiamo iniziato.

La funzione main() itera sopra l'elenco di gestori da installare. Per ogni gestore, per prima cosa chiama signal() per scoprire se il processo sta ignorando il segnale, e mentre lo fa, installa SIG_IGN come gestore, il che garantisce che il segnale rimanga ignorato. Se il segnale non è stato ignorato in precedenza, chiama di nuovo signal(), questa volta per installare il gestore di segnale preferito. (L'altro valore è presumibilmente SIG_DFL, il gestore di segnale predefinito per il segnale.) Poiché la prima chiamata a "signal()" imposta il gestore su SIG_IGN e signal() restituisce il gestore errori precedente, il valore di old dopo l'istruzione if deve essere SIG_IGN - da qui l'affermazione. (Beh, potrebbe essere SIG_ERR se qualcosa è andato drammaticamente sbagliato - ma poi l'avrei saputo dall'assertimento.)

Il programma quindi fa le sue cose ed esce normalmente.

Si noti che il nome di una funzione può essere considerato come un puntatore a una funzione del tipo appropriato. Quando non si applicano le parentesi di chiamata di funzione, ad esempio negli inizializzatori, il nome della funzione diventa un puntatore di funzione. Questo è anche il motivo per cui è ragionevole richiamare funzioni tramite la notazione pointertofunction(arg1, arg2); quando vedi alarm_handler(1), puoi considerare che alarm_handler è un puntatore alla funzione e quindi alarm_handler(1) è una chiamata di una funzione tramite un puntatore di funzione.

Così, finora, ho mostrato che una variabile SignalHandler è relativamente straight-forward di utilizzare, fino a quando si dispone di alcuni del giusto tipo di valore da assegnare ad essa - che è ciò che i due funzioni di gestione del segnale fornire.

Ora torniamo alla domanda: in che modo le due dichiarazioni per signal() riguardano l'una con l'altra.

Rivediamo la seconda dichiarazione:

extern SignalHandler signal(int signum, SignalHandler handler); 

Se abbiamo cambiato il nome della funzione e il tipo in questo modo:

extern double function(int num1, double num2); 

si dovrebbe avere nessun problema interpretare questo come una funzione che prende un int e un double come argomenti e restituisce un valore double (sarebbe? forse sarebbe meglio non "risolvere se questo è problematico - ma forse dovresti essere cauto nel fare domande così difficili come questo se è un problema).

Ora, invece di essere un double, la funzione signal() accetta il SignalHandler come secondo argomento e ne restituisce uno come risultato.

La meccanica con cui che può anche essere trattati come:

extern void (*signal(int signum, void(*handler)(int signum)))(int signum); 

sono difficili da spiegare - così io probabilmente rovinare tutto. Questa volta ho dato i nomi dei parametri, anche se i nomi non sono critici.

In generale, in C, il meccanismo di dichiarazione è tale che se si scrive:

type var; 

poi quando si scrive var che rappresenta un valore del dato type. Ad esempio:

int  i;   // i is an int 
int *ip;   // *ip is an int, so ip is a pointer to an integer 
int  abs(int val); // abs(-1) is an int, so abs is a (pointer to a) 
         // function returning an int and taking an int argument 

Nello standard, typedef è trattata come una classe di memoria nella grammatica, simile static e extern sono classi di memoria.

typedef void (*SignalHandler)(int signum); 

significa che quando si vede una variabile di tipo SignalHandler (diciamo alarm_handler) invocata come:

(*alarm_handler)(-1); 

il risultato ha type void - non v'è alcun risultato. E (*alarm_handler)(-1); è una chiamata di alarm_handler() con argomento -1.

Quindi, se abbiamo dichiarato:

extern SignalHandler alt_signal(void); 

significa che:

(*alt_signal)(); 

rappresenta un valore nullo. E quindi:

extern void (*alt_signal(void))(int signum); 

è equivalente. Ora, signal() è più complessa, perché non solo restituisce un SignalHandler, accetta anche sia un int e un SignalHandler come argomenti:

extern void (*signal(int signum, SignalHandler handler))(int signum); 

extern void (*signal(int signum, void (*handler)(int signum)))(int signum); 

Se che ancora si confonde, non sono sicuro di come aiutare - è ancora a qualche livello misterioso per me, ma mi sono abituato a come funziona e posso quindi dirti che se ti attacchi per altri 25 anni o giù di lì, diventerà una seconda natura per te (e forse anche un po 'più veloce se sei intelligente).

+19

Pensavo di saper leggere praticamente qualsiasi codice C ma questa funzione di segnale mi dava problemi! Buona risposta – toto

+1

Ho visto questa spiegazione prima. E poi, come è il caso ora, Penso che ciò che non ho ottenuto sia la connessione tra le due affermazioni: extern void (* signal (int, void (*) (int))) (int);/* e */ typedef void (* SignalHandler) (int signum); segnale SignalHandler extern (int signum, SignalHandler handler); Oppure, quello che voglio chiedere è, qual è il concetto di base che si può usare per venire con la seconda versione che hai? Qual è il fondamento che collega "SignalHandler" e il primo typedef? Penso che ciò che deve essere spiegato qui è ciò che sta facendo tipicamente typedef. Thx –

+0

http://en.wikipedia.org/wiki/Typedef – psychotik

22

Un modo molto semplice per capire typedef di puntatore a funzione:

int add(int a, int b) 
{ 
    return (a+b); 
} 

typedef int (*add_integer)(int, int); //declaration of function pointer 

int main() 
{ 
    add_integer addition = add; //typedef assigns a new variable i.e. "addition" to original function "add" 
    int c = addition(11, 11); //calling function via new variable 
    printf("%d",c); 
    return 0; 
} 
10
int add(int a, int b) 
{ 
    return (a+b); 
} 
int minus(int a, int b) 
{ 
    return (a-b); 
} 

typedef int (*math_func)(int, int); //declaration of function pointer 

int main() 
{ 
    math_func addition = add; //typedef assigns a new variable i.e. "addition" to original function "add" 
    math_func substract = minus; //typedef assigns a new variable i.e. "substract" to original function "minus" 

    int c = addition(11, 11); //calling function via new variable 
    printf("%d\n",c); 
    c = substract(11, 5); //calling function via new variable 
    printf("%d",c); 
    return 0; 
} 

uscita di questo è:

noti che, stesso math_func definer è stato utilizzato per la dichiarazione di entrambe le funzioni.

Stesso approccio di typedef può essere utilizzato per struct extern. (Utilizzando sturuct in un altro file.)

3

Questo è l'esempio più semplice di puntatori a funzione e array puntatore a funzione che ho scritto come un esercizio.

typedef double (*pf)(double x); /*this defines a type pf */ 

    double f1(double x) { return(x+x);} 
    double f2(double x) { return(x*x);} 

    pf pa[] = {f1, f2}; 


    main() 
    { 
     pf p; 

     p = pa[0]; 
     printf("%f\n", p(3.0)); 
     p = pa[1]; 
     printf("%f\n", p(3.0)); 
    } 
0

Usa typedef per definire i tipi più complicati cioè puntatori di funzione

porterò l'esempio di definire una macchina a stati in C

typedef int (*action_handler_t)(void *ctx, void *data); 

ora abbiamo definito un tipo chiamato action_handler che prende due puntatori e restituisce un numero

definisce il tuo stato-macchina

Il puntatore di funzione all'azione ha l'aspetto di un tipo semplice e typedef serve principalmente a questo scopo.

Tutti i miei gestori di eventi ora devono aderire al tipo definito dal action_handler

int handle_event_a(void *fsm_ctx, void *in_msg); 

    int handle_event_b(void *fsm_ctx, void *in_msg); 

Riferimenti:

Esperto di programmazione C da Linden

Problemi correlati