2010-07-09 21 views
10

Con alcune istruzioni di montaggio e programmi C, posso visualizzare come sarebbe una funzione compilata, ma è buffo che non abbia mai pensato così attentamente a come sarebbe una classe C++ compilata.Che aspetto ha una classe C++ compilata?

bash$ cat class.cpp 
#include<iostream> 
class Base 
{ 
    int i; 
    float f; 
}; 

bash$ g++ -c class.cpp 

mi sono imbattuto:

bash$objdump -d class.o 
bash$readelf -a class.o 

ma quello che mi è difficile per me capire.

Qualcuno potrebbe spiegarmi o suggerire alcuni buoni punti di partenza.

+1

Apparirà più o meno come una struttura C. –

+0

Giusto !. Quindi questo mi ha portato a pensare come sarebbero state compilate le strutture, e ho capito che forse non capisco questa parte, tanto per cominciare. bash $ cat struct.cpp struct test { int i; float f; }; Che cosa dovrebbe contenere il file corrispondente a questo file? Capisco, i file oggetto corrispondenti alle strutture non assomiglieranno a quelli delle funzioni nel senso che non ci sono istruzioni di assemblaggio. Grazie, – xyz

+0

in particolare, questa struct.cpp non dovrebbe avere né un testo né una sezione di dati nei file oggetto? Non lo so, se questa domanda si sta piegando di più verso il formato dei file elf. – xyz

risposta

0

provare il

g ++ -S class.cpp

che vi darà un file di assieme 'class.s' (file di testo), che si può leggere con un editor di testo. Tuttavia, il tuo codice non fa nulla (dichiarare che una classe non genera codice da solo), quindi non avrai molto nel file assembly.

2

ok. non c'è nulla di speciale con le classi compilate. anche le classi compilate non esistono. ciò che esiste sono oggetti che sono pezzi di memoria piatta con possibili risvolti tra i campi? e il membro autonomo funziona da qualche parte nel codice che porta il puntatore a un oggetto come primo parametro.

quindi oggetto della classe base dovrebbe essere qualcosa

(* base_address): i (* base_address + sizeof (int)): f

è possibile avere imbottiture tra i campi? ma questo è specifico per l'hardware. basato sul modello di memoria dei processori.

anche ... nella versione di debug è possibile rilevare la descrizione della classe nei simboli di debug. ma questo è specifico del compilatore. dovresti cercare un programma che scarichi i simboli di debug per il tuo compilatore.

2

"Classi compilate" significa "metodi compilati".

Un metodo è una funzione ordinaria con un parametro aggiuntivo, di solito inserito in un registro (principalmente% ecx, credo, questo è vero almeno per la maggior parte dei compilatori di Windows che devono produrre oggetti COM utilizzando __qualsiasi convenzione).

Quindi le classi C++ non sono molto diverse da un gruppo di funzioni ordinarie, ad eccezione del nome mangling e della magia nei costruttori/distruttori per la creazione di vtables.

19

Le classi sono (più o meno) costruite come strutture normali. I metodi sono (più o meno ...) convertiti in funzioni il cui primo parametro è "questo". I riferimenti alle variabili di classe vengono eseguiti come offset di "questo".

Per quanto riguarda l'ereditarietà, è possibile citare le domande frequenti in C++ LITE, che è speculare qui http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4. Questo capitolo mostra come funzioni virtuali sono chiamati in hardware reale (quello che fa il compilazione fare in codice macchina


Lavoriamo un esempio Supponiamo Classe base ha 5 funzioni virtuali:.. virt0() attraverso virt4().

// Your original C++ source code 
class Base { 
public: 
    virtual arbitrary_return_type virt0(...arbitrary params...); 
    virtual arbitrary_return_type virt1(...arbitrary params...); 
    virtual arbitrary_return_type virt2(...arbitrary params...); 
    virtual arbitrary_return_type virt3(...arbitrary params...); 
    virtual arbitrary_return_type virt4(...arbitrary params...); 
    ... 
}; 

Passo # 1: il compilatore costruisce una tabella statica che contiene 5 funzioni-puntatori, seppellendo quel tavolo nella memoria statica da qualche parte. Molti compilatori (non tutti) definiscono questa tabella durante la compilazione del file .cpp che definisce la prima funzione virtuale non in linea di Base. Chiamiamo la tabella v-table; facciamo finta che il suo nome tecnico sia Base::__vtable. Se un puntatore di funzione si inserisce in una parola macchina sulla piattaforma hardware di destinazione, Base::__vtable finirà col consumare 5 parole nascoste di memoria. Non 5 per istanza, non 5 per funzione; appena 5. Potrebbe sembrare qualcosa di simile al seguente pseudo-codice:

// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp 

// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Base::__vtable[5] = { 
    &Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4 
}; 

Passo # 2: il compilatore aggiunge un puntatore nascosta (di solito anche una macchina-word) a ciascun oggetto della classe base. Questo è chiamato il v-pointer. Pensate a questo puntatore nascosta come membro dati nascosti, come se il compilatore riscrive la classe a qualcosa di simile:

// Your original C++ source code 
class Base { 
public: 
    ... 
    FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer 
    ... 
}; 

Step # 3: il compilatore inizializza this->__vptr all'interno di ogni costruttore. L'idea è quella di provocare v-pointer di ciascun oggetto per puntare a v-table della sua classe, come se si aggiunge la seguente istruzione in init-list di ogni costruttore:

Base::Base(...arbitrary params...) 
    : __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer 
    ... 
{ 
    ... 
} 

Ora cerchiamo di lavorare fuori una classe derivata. Supponiamo che il tuo codice C++ definisca la classe Der che eredita dalla classe Base. Il compilatore ripete i passaggi 1 e 3 (ma non # 2). Al punto # 1, il compilatore crea una v-table nascosta, mantenendo gli stessi puntatori funzione come in Base::__vtable ma sostituendo gli slot corrispondenti alle sostituzioni. Per esempio, se Der sovrascrive virt0() attraverso virt2() ed eredita gli altri come-si, v-table di Der potrebbe essere simile a questa (finta Der non aggiunge alcuna nuova pacchetti virtuali):

// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp 

// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Der::__vtable[5] = { 
    &Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4 
};          ^^^^----------^^^^---inherited as-is 

Nel passo # 3, il compilatore aggiunge un'assegnazione puntatore simile all'inizio di ciascuno dei costruttori di Der. L'idea è di cambiare il v-pointer di ogni oggetto Der in modo che punti alla v-table della sua classe. (Questo non è un secondo puntatore v, è lo stesso v-pointer definito nella classe base, Base; ricorda, il compilatore non ripete il passaggio n. 2 nella classe Der.)

Infine, vediamo come il compilatore implementa una chiamata a una funzione virtuale. Il vostro codice potrebbe essere simile a questo:

// Your original C++ code 
void mycode(Base* p) 
{ 
    p->virt3(); 
} 

Il compilatore non ha idea se questo sta andando a chiamare Base::virt3() o Der::virt3() o forse il metodo di un'altra classe derivata che non ha nemmeno esiste ancora virt3(). Si sa solo che si sta chiamando virt3(), che capita di essere la funzione nello slot n. 3 della v-table. Si riscrive la chiamata in qualcosa di simile a questo:

// Pseudo-code that the compiler generates from your C++ 

void mycode(Base* p) 
{ 
    p->__vptr[3](p); 
} 

vi consiglio vivamente di ogni sviluppatore C++ per leggere le FAQ. Potrebbero essere necessarie diverse settimane (dato che è difficile da leggere e molto tempo), ma ti insegnerà molto sul C++ e su cosa può essere fatto con esso.

1

La principale differenza dalla lettura dei file oggetto C è che i nomi dei metodi C++ sono mangled. Puoi provare a utilizzare l'opzione -C|--demangle con objdump.

0

Come una struttura C e un insieme di funzioni con un parametro aggiuntivo che è un puntatore alla struttura.

Il modo più semplice per seguire ciò che il compilatore ha fatto forse è quello di costruire senza ottimizzazione, quindi caricare il codice in un debugger e passo attraverso di essa con modalità sorgente/assemblatore mista.

Tuttavia, il punto del compilatore è che non è necessario conoscere questa roba (a meno che forse non si stia scrivendo un compilatore).

Problemi correlati