2012-09-30 14 views
9

Consente di dire che abbiamo una classeIn che modo C++ memorizza funzioni e oggetti in memoria?

class A 
{ 
    int x; 
public: 
    void sayHi() 
    { 
     cout<<"Hi"; 
    } 
}; 

int main() 
{ 
    A *a=NULL; 
    a->sayHi(); 
} 

Il codice qui sopra compilare su Turbo C (dove ho testato) e stampa Hi come output.

Mi aspettavo un incidente perché a è NULL. Più sopra se faccio sayHi() funzione virtuale, si dice

Abnormal temination(Segmentation fault in gcc) 

Conosco un sacco di esso dipende dall'implementazione, ma se qualcuno potesse far luce su qualsiasi implementazione o semplicemente dare una visione d'insieme che sarebbe stato davvero bello.

+2

La chiamata di un metodo tramite un puntatore nullo è un comportamento non definito. Tutto può accadere, non deve crollare, ma lo standard lo consente. –

+0

Non un ragazzo C++, quindi questa è una supposizione, ma: il tuo codice non ha bisogno di accedere a nessuna memoria di un'istanza di 'A'. 'sayHi()' non usa il campo 'x', e non è virtuale, quindi non ha bisogno di accedere al vtable per risolvere. Un compilatore C++ dovrebbe effettivamente inserire un controllo per vedere se 'a' è un puntatore valido per causare un errore. – millimoose

risposta

6

In C++, i metodi di una classe non sono memorizzati all'interno delle istanze di quella classe. Sono semplicemente alcune funzioni "speciali" che accettano in modo trasparente il puntatore this oltre agli argomenti specificati dal programmatore.

Nel tuo caso, il metodo sayHi() non fa riferimento a nessuno dei campi della classe, pertanto il puntatore this (che è NULL) non viene mai seguito.

Non commettere errori, tuttavia, questo comportamento è ancora indefinito. Il tuo programma può scegliere di inviare e-mail cattive al tuo elenco di contatti quando invochi questo. In questo caso particolare, fa la cosa peggiore e sembra funzionare.

Il metodo virtual è stato aggiunto poiché ho risposto alla domanda, ma non perfezionerò la mia risposta, poiché è inclusa nelle risposte di altri.

+1

+1 per sottolineare che un comportamento indefinito che "funziona" è BAD. –

7

Ovviamente, il codice ha un comportamento non definito, ad esempio, qualunque cosa si ottiene è un caso. Detto questo, il sistema non ha bisogno di conoscere l'oggetto quando chiama una funzione membro non virtuale: può essere chiamato solo in base alla firma. Inoltre, se una funzione membro non ha bisogno di accedere a un membro, non ha davvero bisogno di un oggetto e può semplicemente funzionare. Questo è ciò che hai osservato quando il codice ha stampato un output. Se questo è il modo in cui il sistema è implementato non è definito, tuttavia, cioè, nulla dice che funzioni.

Quando si chiama un sistema di tipo di funzione virtuale, inizia a guardare un record di informazioni sul tipo associato all'oggetto. Quando si chiama una funzione virtuale su un puntatore NULL, non esiste alcuna informazione di questo tipo e il tentativo di accedervi probabilmente causa una sorta di arresto anomalo. Tuttavia, non è necessario, ma lo fa per la maggior parte del sistema.

BTW, main()sempre restituisce int.

1

Se si chiama un metodo non virtuale di una classe, per il compilatore è sufficiente sapere a quale classe appartiene la funzione e tramite il dereferenziazione, sebbene un puntatore NULL a una classe per chiamare il metodo, il compilatore ottiene quell'informazione. Il metodo sayHi() è praticamente solo una funzione che porta il puntatore all'istanza della classe come parametro nascosto. Questo puntatore è NULL ma non importa se non si fa riferimento ad alcun attributo nel metodo.

Nel momento in cui si rende questo metodo virtuale, la situazione cambia. Il compilatore non sa più quale codice è associato al metodo in fase di compilazione e deve capirlo al momento dell'esecuzione.Quello che fa è che guarda in una tabella che contiene fondamentalmente dei puntatori di funzioni per tutti i metodi virtuali; questa tabella è associata all'istanza della classe in modo che guardi il pezzo della memoria relativo al puntatore NULL e quindi si blocca in questo caso.

3

Come una generalizzazione, il layout di un oggetto istanziato da una classe senza super classi e funzioni virtuali è come segue:

* - v_ptr ---> * pTypeInfo 
|    |- pVirtualFuncA 
|    |- pVirtualFuncB 
|- MemberVariableA 
|- MemberVariableB 

v_ptr è un puntatore alla v-table - che contiene gli indirizzi di virtuale funzioni e dati RTTI per l'oggetto. Le classi senza funzioni virtuali non hanno tabelle virtuali.

Nell'esempio sopra riportato, class A non ha metodi virtuali e quindi non v-table. Ciò significa che l'implementazione di sayHi() da chiamare può essere determinata al momento della compilazione ed è invariabile.

Il compilatore genera il codice che imposta il puntatore this implicito su a e quindi salta all'inizio di sayHi(). Poiché l'implementazione non ha bisogno del contenuto dell'oggetto, il fatto che funzioni quando il puntatore è NULL è una felice coincidenza.

Se si desidera rendere virtuale sayHi(), il compilatore non può determinare l'implementazione da chiamare al momento del compilatore, quindi genera codice che cerca l'indirizzo della funzione nella tabella v e lo chiama. Nell'esempio in cui a è NULL, il compilatore legge il contenuto dell'indirizzo 0, provocando l'interruzione.

+0

Il fatto che la classe A non abbia metodi virtuali non è rilevante. Ciò che è importante è che la particolare funzione chiamata, sayHi, non è una funzione virtuale e non usa membri dell'oggetto classe. Quindi questa funzione può essere (ed è) chiamata senza usare il puntatore all'oggetto classe. –