2010-09-03 19 views
5

Consente di avere questa situazione (in C++, C# in classi A, B sono interfacce):Come vengono implementati i vtables in C++ e C#?

class A { virtual void func() = 0; }; 
class B { virtual void func() = 0; }; 
class X: public A, public B { virtual void func(){ var = 1; } int var;}; 

X * x = new X; // from what I know, x have 2 vtables, is this the same in c#? 
A * a = (A*)x; // a == x 
B * b = (B*)x; // here b != x, so when calling b->func(), how is the address of var correct? 

fa il compilatore C# creare sempre un vtable? Fa qualche correzione puntatore durante il casting?

+2

Suppongo che intendiate 'a == x' e' b! = X'. Poiché i puntatori vengono assegnati uno dopo l'altro sullo stack, hanno tutti un indirizzo distinto ... anche se dovrebbero puntare tutti allo stesso oggetto. – PypeBros

+0

ovviamente, errore mio :) È stato risolto ora, ho dimenticato che a == b compara gli indirizzi – chris

risposta

2

Non essere eccessivamente pedante, ma il compilatore C# non viene coinvolto a questo livello. L'intero modello di tipo, l'ereditarietà, l'implementazione dell'interfaccia, ecc., Viene effettivamente gestito dal CLR in particolare dal CTS (Common Type System). I compilatori .NET in genere generano solo il codice IL che rappresenta l'intento che viene successivamente eseguito dal CLR in cui viene gestita tutta la gestione Vtable, ecc.

Per alcuni dettagli su come il CLR crea e gestisce i tipi di runtime, il seguente collegamento sarà un buon punto di partenza. Verso la fine sono spiegate le mappe MethodTable e Interface.

http://web.archive.org/web/20150515023057/https://msdn.microsoft.com/en-us/magazine/cc163791.aspx

+0

Questo link è rotto ora - va solo a un indice dei numeri arretrati della rivista MSDN. Per vedere l'articolo originale, scaricare il file .CHM per maggio 2005, sbloccarlo e quindi andare all'articolo "JIT ed Esegui: Drill Into .NET Framework Internals per vedere come il CLR crea oggetti runtime". (In alternativa, google il titolo.) Mi piacerebbe modificare in un link, ma non sembra essere un "ufficiale". –

+0

Grazie, ho sostituito il link con un link all'articolo originale tramite la macchina del wayback time. –

2

sì, c'è sempre e solo un v-table in un linguaggio gestito, il CLR non supporta l'ereditarietà multipla. C'è una correzione puntatore quando si esegue il cast su un'interfaccia implementata.

Questo è un problema notevole quando si tenta di dichiarare un'interfaccia COM dichiarata da un'altra interfaccia oltre a IUnknown. Un problema non del tutto compreso dall'autore this article's. COM richiede una v-table separata per ogni interfaccia , solo quello che fa un compilatore che supporta MI.

+0

Se esiste un solo vtable, in che modo C# supporta l'ereditarietà dell'interfaccia diamante? – chris

+1

@Chris, se dai un'occhiata al link che ho fornito nella mia risposta dovresti avere un'idea di base su come funziona.Ecco una citazione "La duplicazione di slot è necessaria per creare l'illusione che ogni interfaccia abbia il proprio mini vtable, tuttavia gli slot duplicati puntano alla stessa implementazione fisica." –

+2

Il diamante causa problemi perché diventa ambiguo quale * implementazione * usare. Un'interfaccia non ha implementazione. Ci può essere solo una classe base. –

3

Se studio questa versione derivata con g ++

class X: public A, public B { 
    unsigned magic; 
public: 
    X() : magic(0xcafebabe) {}; 
    virtual void func(){ var = 1; } int var; 
}; 

extern "C" int main() 
{ 
    X * x = new X; // from what I know, x have 2 vtables, is this the same in c#? 
    A * a = (A*)x; // &a == &x 
    B * b = (B*)x; // here &b != &x, so when calling b->func(), how is the address of var correct? 
    printf("%p -- %p -- %p\n", x, a, b); 

    unsigned* p = (unsigned*)((void*) x); 
    unsigned *q = (unsigned*)(p[1]); 
    printf("x=[%x %x %x %x]\n",p[0],p[1],p[2],p[3]); 
    p = (unsigned*)(p[0]); 
    printf("a=[%x %x %x %x]\n",p[0],p[1],p[2],p[3]); 
    printf("b=[%x %x %x %x]\n",q[0],q[1],q[2],q[3]); 

} 

Risulta che, in C++ b == a + 1, quindi la struttura per X è [vtable-X + A] [vtable-B ] [magic] [var] inspecting deeper (nm ./a.out), vtable-X + a contiene il riferimento verso X :: func (come ci si aspetterebbe). quando hai lanciato la tua X in B, ha regolato i puntatori in modo che le funzioni VTBL per B appiano dove il codice si aspetta.

In realtà intendevi "nascondere" B :: func()?

Il vtbl di B ha l'aspetto di mantenere un riferimento verso un "trampolino" su X che ripristina il puntatore dell'oggetto su una X completa prima di chiamare il "normale" X :: func che contiene X + A vtbl.

080487ea <_ZThn8_N1X4funcEv>: # in "X-B vtbl" 
_ZThn8_N1X4funcEv(): 
80487ea:  83 44 24 04 f8   addl $0xfffffff8,0x4(%esp) 
80487ef:  eb 01     jmp 80487f2 <_ZN1X4funcEv> 
80487f1:  90      nop 

080487f2 <_ZN1X4funcEv>:  # in X-A vtbl 
_ZN1X4funcEv(): 
80487f2:  55      push %ebp 
80487f3:  89 e5     mov %esp,%ebp 
80487f5:  8b 45 08    mov 0x8(%ebp),%eax 
80487f8:  c7 40 14 01 00 00 00 movl $0x1,0x14(%eax) 
80487ff:  5d      pop %ebp 
8048800:  c3      ret  
+2

Hai davvero solo "extern" C "' main? –

+2

Hmm ... non so. Sembra che non sia qualcosa che di solito faccio nei miei altri strumenti C++. Questo riguarda solo il nome di mangling e visibilità esterna, giusto? – PypeBros

+0

Così divertente qui)) – rostamn739

1

i vtables sono un dettaglio di implementazione. Non esiste alcuna implementazione ufficiale/richiesta/prevista. Diversi produttori di compilatori possono implementare l'ereditarietà in modo diverso.

Problemi correlati