2013-08-16 12 views
5
#include <stdio.h> 
#include <stdlib.h> 

int (*fptr1)(int); 

int square(int num){ 
    return num*num; 
} 

void main(){ 
    fptr1 = &square; 
    printf("%d\n",fptr1(5)); 
} 

Qualcuno può spiegare brevemente cosa succede nello stack quando chiamiamo un puntatore a funzione? Qual è la differenza tra chiamare una funzione direttamente in main() e chiamarla per puntatore a funzione in linguaggio C mediante la memoria fisica e il processo?Funzione Pointer Memory Spiegazione in C

Ho cercato di capire cosa succede in memoria quando chiamiamo una funzione con un puntatore a funzione, ma non è abbastanza per me.

  1. Quando chiamiamo una funzione con il puntatore, il puntatore ha la posizione di questa funzione nello spazio del codice?
  2. Quando la funzione chiamata è in esecuzione è uguale alla funzione normalmente chiamata in main()?
  3. Qual è la differenza della funzione di chiamata direttamente o utilizzando il puntatore di funzione quando il codice è in esecuzione in un processore predittivo di ramo pipeline?
+1

[Ecco un progetto per aiutare a spiegare l'utilità dei puntatori di funzione] (http://www.cs.rit.edu/~ats/books/ooc.pdf) –

+2

Si noti che 'void main()' non è valido in C (o C++) - non in standard C, in C basata su Unix, né in Microsoft C. Dovrebbe essere 'int main()'. –

+0

Grazie per l'avviso su main – cycrel

risposta

10

Il modo migliore per rispondere a questa è quello di guardare lo smontaggio (campione leggermente modificata):

fptr1 = &square; 
int result1 = fptr1(5); 
int result2 = square(5); 

I risultati in questo x64 ASM:

fptr1 = &square; 
000000013FA31A61 lea   rax,[square (013FA31037h)] 
000000013FA31A68 mov   qword ptr [fptr1 (013FA40290h)],rax 
    int result1 = fptr1(5); 
000000013FA31A6F mov   ecx,5 
000000013FA31A74 call  qword ptr [fptr1 (013FA40290h)] 
000000013FA31A7A mov   dword ptr [result1],eax 
    int result2 = square(5); 
000000013FA31A7E mov   ecx,5 
000000013FA31A83 call  square (013FA31037h) 
000000013FA31A88 mov   dword ptr [result2],eax 

Come si può vedere l'assemblaggio è praticamente identico tra chiamare la funzione direttamente e tramite un puntatore.In entrambi i casi, la CPU deve avere accesso alla posizione in cui si trova il codice e chiamarla. La chiamata diretta ha il vantaggio di non dover dereferenziare il puntatore (poiché l'offset verrà inserito nell'assieme).

  1. Sì, si può vedere nella assegnazione del puntatore a funzione, che memorizza l'indirizzo codice della funzione 'piazza'.
  2. Da una pila installazione/demolizione: sì. Dal punto di vista delle prestazioni, vi è una leggera differenza come indicato sopra.
  3. Non ci sono rami, quindi non c'è differenza qui.

Edit: Se dovessimo interporre rami nel campione di cui sopra, non ci sarebbe voluto molto tempo per esaurire gli scenari interessanti, così io li affrontare qui:

Nel caso in cui ci avere un ramo prima di caricare (o assegnazione) del puntatore funzione, per esempio (a montaggio pseudo):

branch zero foobar 
lea square 
call ptr 

Poi potrebbe avere una differenza. Supponiamo che la pipeline abbia scelto di caricare e avviare l'elaborazione delle istruzioni su foobar, quindi quando ha capito che non avremmo effettivamente preso quel ramo, dovrebbe essere bloccato per caricare il puntatore di funzione e dereferenziarlo. Se stessimo solo chiamando un indirizzo di conoscenza, non ci sarebbe stato uno stallo.

Caso due:

lea square 
branch zero foobar 
call ptr 

In questo caso non ci sarebbe alcuna differenza tra le chiamate dirette vs attraverso un puntatore a funzione, come tutto ciò che abbiamo bisogno è già sapere se il processore inizia l'esecuzione sulla strada sbagliata e quindi si reimposta per avviare l'esecuzione della chiamata.

Il terzo scenario è quando il ramo segue la chiamata, e ovviamente non è molto interessante da una prospettiva della pipeline dato che abbiamo già eseguito la subroutine.

Quindi per rispondere completamente alla domanda , direi Sì, c'è una differenza. Ma la vera domanda è se il compilatore/ottimizzatore è abbastanza intelligente da spostare il ramo dopo il l'assegnazione del puntatore di funzione, così rientra nel caso 2 e non nel caso 1.

+0

Grazie per i tuoi sforzi mi ha aiutato così tanto a capire – cycrel

3
  1. Assolutamente
  2. Nessuna differenza

Chiamare una funzione tramite un puntatore a funzione è solo una comodità; e rende possibile passare una funzione come parametro ad un'altra funzione. Vedere ad esempio, qsort()

+0

Non sarei assolutamente sicuro di 3. Se il valore del puntatore cambia spesso in un modo imprevedibile (per il processore), la previsione del ramo potrebbe essere sbagliata in più casi. – FrankPl

+0

Grazie mille per l'aiuto – cycrel

2

Non c'è differenza tra chiamare una funzione con il suo nome e chiamarla tramite un puntatore a funzione. Lo scopo dei puntatori di funzione è che puoi chiamare una funzione che viene specificata da qualche altra parte. Ad esempio, la funzione accetta un puntatore a funzione come argomento che chiama per determinare come ordinare gli elementi dell'array, anziché avere un solo metodo di confronto.

+0

Questo è un buon esempio, ci sono alcuni altri vantaggi che ho appreso da [Per quanto riguarda i puntatori di funzione nei sistemi embedded] (http://stackoverflow.com/questions/4836245/funzionidispositivo-in-embedded-sistemi- sono-loro-utili) –

5
  1. Il puntatore alla funzione contiene l'indirizzo dell'inizio della funzione nel segmento di testo del programma.
  2. Una volta chiamata, la funzione viene eseguita in modo identico indipendentemente dal fatto che venga chiamata direttamente o da un puntatore alla funzione.
  3. Non ne sono sicuro. Spesso non ci sarà molta differenza; il puntatore alla funzione non cambia molto spesso, se non del tutto (ad esempio perché hai caricato una libreria condivisa dinamicamente, quindi devi usare un puntatore per funzionare per chiamare la funzione).

Qual è la differenza tra chiamare una funzione direttamente nel main() e chiamandolo da puntatore a funzione?

L'unica differenza è probabilmente un riferimento di memoria aggiuntivo per recuperare il puntatore di funzione dalla memoria.

+0

Con "inizio della funzione" intendevi l'indice 0 ° del, diciamo, il "bytecode array" della funzione call/frame? – zgulser

+0

Il codice byte normalmente non è un termine utilizzato con C. L'inizio della funzione è l'indirizzo a cui il programma salta quando viene chiamata la funzione. È normalmente il codice del prologo che regola dove sono memorizzate le variabili, ecc., Che è approssimativamente ciò che è il frame della chiamata. –

+0

Certo, non c'è nulla di simile in C. Volevo solo visualizzare/materializzare la cosa "dell'indirizzo" che viene puntata da un puntatore a funzione. Penso che in ogni caso, il flusso deve fare un po 'di inizializzazione per il frame/chiamata della funzione in entrata (e probabilmente impostare PC sul valore del puntatore della funzione sottostante) prima di andare oltre. – zgulser