2014-07-01 13 views
10

Supponiamo di avere una classe:Dove si trovano le funzioni di un oggetto memorizzate?

class Foo 
{ 
private: 
    int a; 
public: 
    void func() 
    { 
     a = 0; 
     printf("In Func\n"); 
    } 
} 

int main() 
{ 
    Foo *foo = new Foo(); 
    foo->func(); 
    return 0; 
} 

Quando viene creato l'oggetto della classe Foo e inizializzata, comprendo che un intero assumerà 4 byte di memoria. Come viene memorizzata la funzione? Cosa succede in memoria/stack/registri/con il contatore del programma quando si chiama foo-> func()?

risposta

8

La tua funzione non è virtuale, viene quindi denominata staticamente: il compilatore inserisce un salto nel segmento di codice corrispondente alla tua funzione. Nessuna memoria aggiuntiva viene utilizzata per istanza.

Se la tua funzione fosse virtuale, l'istanza porterebbe un vpointer, che verrebbe sottoposto a dereferenziazione per trovare la sua classe 'vtable, che verrebbe quindi indicizzata per trovare il puntatore alla funzione da chiamare e infine saltare lì. Il costo aggiuntivo è quindi di un vtable per classe (probabilmente la dimensione di un puntatore a funzione, moltiplicato per il numero di funzioni virtuali della classe) e un puntatore per istanza.

Si noti che questa è un'implementazione comune di chiamate virtuali, ma non è in alcun modo applicata dallo standard, quindi in realtà non può essere implementata in questo modo, ma le probabilità sono abbastanza buone. Il compilatore può anche ignorare del tutto il sistema di chiamate virtuali se ha conoscenza del tipo statico della tua istanza al momento della compilazione.

+0

"il compilatore inserisce un salto nel segmento di codice". Dove sono archiviate queste informazioni di salto? – shaveenk

+0

@shaveenk Direttamente sul sito di chiamata, ovvero un'altra parte del segmento di codice. Per una chiamata statica diventa hardcoded, quindi si finisce semplicemente con un semplice goto 0xSomewhere; nell'assemblaggio (dopo alcuni parametri di regolazione) – Quentin

2

Le funzioni membro sono come le normali funzioni, sono memorizzate nella sezione "codice" o "testo". C'è una cosa speciale con le funzioni membro (non statiche), e questo è l'argomento "nascosto" this che viene passato insieme alla funzione. Quindi nel tuo caso, l'indirizzo in foo verrà passato a func.

Esattamente come viene passato quell'argomento e ciò che accade ai registri e allo stack è definito dall'ABI (Application Binary Interface) e varia da processore a processore. Non esiste una definizione rigida per questo, a meno che non ci dica quale sia la combinazione di compilatore, sistema operativo e processore utilizzato (e supponendo che le informazioni siano quindi pubblicamente disponibili - non tutti i produttori di compilatori/OS lo diranno molto chiaramente). Ad esempio, x86-64 utilizzerà RCX per this su Windows e RDI su Linux e l'istruzione di chiamata inserirà automaticamente l'indirizzo di ritorno nello stack. Su un processore ARM [in Linux, ma penso che lo stesso si applichi a Windows, non l'ho mai guardato], R0 è usato per il puntatore this e l'istruzione BX usata per la chiamata, che come parte di esso memorizza lr con il pc dell'istruzione per tornare a. lr quindi deve essere salvato [probabilmente in pila] in func, poiché chiama printf.

13

La risposta breve: Sarà memorizzata nella sezione di testo o codice del file binario solo una volta, indipendentemente dal numero di istanze della classe creata.

Le funzioni non vengono memorizzate separatamente in nessun punto per ciascuna istanza di una classe. Sono trattati allo stesso modo di qualsiasi altra funzione non membro. L'unica differenza è che il compilatore aggiunge effettivamente un parametro aggiuntivo alla funzione, che è un puntatore del tipo di classe. Per esempio il compilatore genera il prototipo di funzione in questo modo:

void func(Foo* this); 

(Si noti che questo non può essere la firma definitiva.La firma finale può essere molto più criptico a seconda di vari fattori tra cui il compilatore)

Qualsiasi riferimento ad una variabile membro sarà sostituito dal

this-><member> //for your ex. a=0 translates to this->a = 0; 

Così la linea foo-> funz(); corrisponde approssimativamente a:

  1. Spingere il valore di Foo * in pila. #Compiler dipendente
  2. chiamata func che farà sì che il puntatore all'istruzione per saltare l'offset del func nel #architecture eseguibile dipendente Leggi this e this
  3. Func apparirà il valore dalla pila. Qualsiasi ulteriore riferimento a una variabile membro sarebbe preceduto dal dereferenziazione di questo valore
+0

+1; per quanto riguarda il motivo per cui hai citato il termine _segnalazione_, a volte viene usato l'alternativa _prototype_, sebbene questo uso qui possa essere discusso. – legends2k

+0

Dato che ho menzionato anche il tipo restituito, il prototipo dovrebbe essere la parola di scrittura. Modificato la risposta per riflettere lo stesso. – bashrc

+0

un'ottima risposta –

Problemi correlati